diff --git a/infer/src/IR/Struct.ml b/infer/src/IR/Struct.ml index f741413ab..7990a5268 100644 --- a/infer/src/IR/Struct.ml +++ b/infer/src/IR/Struct.ml @@ -12,14 +12,18 @@ type field = Fieldname.t * Typ.t * Annot.Item.t [@@deriving compare] type fields = field list +type java_class_kind = Interface | AbstractClass | NormalClass + (** Type for a structured value. *) type t = { fields: fields (** non-static fields *) ; statics: fields (** static fields *) ; supers: Typ.Name.t list (** superclasses *) + ; subs: Typ.Name.Set.t (** subclasses, initialized after merging type environments *) ; methods: Procname.t list (** methods defined *) ; exported_objc_methods: Procname.t list (** methods in ObjC interface, subset of [methods] *) ; annots: Annot.Item.t (** annotations *) + ; java_class_kind: java_class_kind option (** class kind in Java *) ; dummy: bool (** dummy struct for class including static method *) } type lookup = Typ.Name.t -> t option @@ -56,20 +60,23 @@ let pp pe name f {fields; supers; methods; exported_objc_methods; annots} = let internal_mk_struct ?default ?fields ?statics ?methods ?exported_objc_methods ?supers ?annots - ?dummy () = + ?java_class_kind ?dummy () = let default_ = { fields= [] ; statics= [] ; methods= [] ; exported_objc_methods= [] ; supers= [] + ; subs= Typ.Name.Set.empty ; annots= Annot.Item.empty + ; java_class_kind= None ; dummy= false } in let mk_struct_ ?(default = default_) ?(fields = default.fields) ?(statics = default.statics) ?(methods = default.methods) ?(exported_objc_methods = default.exported_objc_methods) - ?(supers = default.supers) ?(annots = default.annots) ?(dummy = default.dummy) () = - {fields; statics; methods; exported_objc_methods; supers; annots; dummy} + ?(supers = default.supers) ?(subs = default.subs) ?(annots = default.annots) + ?(dummy = default.dummy) () = + {fields; statics; methods; exported_objc_methods; supers; subs; annots; java_class_kind; dummy} in mk_struct_ ?default ?fields ?statics ?methods ?exported_objc_methods ?supers ?annots ?dummy () @@ -142,3 +149,5 @@ let get_field_type_and_annotation ~lookup field_name_to_lookup typ = let is_dummy {dummy} = dummy + +let add_sub sub x = {x with subs= Typ.Name.Set.add sub x.subs} diff --git a/infer/src/IR/Struct.mli b/infer/src/IR/Struct.mli index 2c6740e4f..286c489c5 100644 --- a/infer/src/IR/Struct.mli +++ b/infer/src/IR/Struct.mli @@ -13,14 +13,18 @@ type field = Fieldname.t * Typ.t * Annot.Item.t [@@deriving compare] type fields = field list +type java_class_kind = Interface | AbstractClass | NormalClass + (** Type for a structured value. *) type t = private { fields: fields (** non-static fields *) ; statics: fields (** static fields *) ; supers: Typ.Name.t list (** supers *) + ; subs: Typ.Name.Set.t (** subclasses, initialized after merging type environments *) ; methods: Procname.t list (** methods defined *) ; exported_objc_methods: Procname.t list (** methods in ObjC interface, subset of [methods] *) ; annots: Annot.Item.t (** annotations *) + ; java_class_kind: java_class_kind option (** class kind in Java *) ; dummy: bool (** dummy struct for class including static method *) } type lookup = Typ.Name.t -> t option @@ -38,6 +42,7 @@ val internal_mk_struct : -> ?exported_objc_methods:Procname.t list -> ?supers:Typ.Name.t list -> ?annots:Annot.Item.t + -> ?java_class_kind:java_class_kind -> ?dummy:bool -> unit -> t @@ -60,3 +65,6 @@ val get_field_type_and_annotation : (** Return the type of the field [fn] and its annotation, None if [typ] has no field named [fn] *) val is_dummy : t -> bool + +val add_sub : Typ.Name.t -> t -> t +(** Add a subclass to the struct type *) diff --git a/infer/src/IR/Tenv.ml b/infer/src/IR/Tenv.ml index 722912c76..f2916a51e 100644 --- a/infer/src/IR/Tenv.ml +++ b/infer/src/IR/Tenv.ml @@ -29,11 +29,11 @@ let pp fmt (tenv : t) = let create () = TypenameHash.create 1000 (** Construct a struct type in a type environment *) -let mk_struct tenv ?default ?fields ?statics ?methods ?exported_objc_methods ?supers ?annots ?dummy - name = +let mk_struct tenv ?default ?fields ?statics ?methods ?exported_objc_methods ?supers ?annots + ?java_class_kind ?dummy name = let struct_typ = Struct.internal_mk_struct ?default ?fields ?statics ?methods ?exported_objc_methods ?supers - ?annots ?dummy () + ?annots ?java_class_kind ?dummy () in TypenameHash.replace tenv name struct_typ ; struct_typ @@ -169,6 +169,19 @@ let store_to_filename tenv tenv_filename = if Config.debug_mode then store_debug_file tenv tenv_filename +let init_inheritances tenv = + let sub_to_supers = + TypenameHash.fold (fun sub {Struct.supers} acc -> (sub, supers) :: acc) tenv [] + in + List.iter sub_to_supers ~f:(fun (sub, supers) -> + List.iter supers ~f:(fun super -> + (* Ignore the super class of java.lang.Object since its sub-classes are too many, which + harms the analysis precision. *) + if not (Typ.Name.equal super Typ.Name.Java.java_lang_object) then + Option.iter (lookup tenv super) ~f:(fun super_struct -> + Struct.add_sub sub super_struct |> TypenameHash.replace tenv super ) ) ) + + let store_global tenv = (* update in-memory global tenv for later uses by this process, e.g. in single-core mode the frontend and backend run in the same process *) @@ -177,5 +190,52 @@ let store_global tenv = let tenv = TypenameHashNormalizer.normalize tenv in L.debug Capture Quiet "Tenv.store: canonicalized tenv has size %d bytes.@." (Obj.(reachable_words (repr tenv)) * (Sys.word_size / 8)) ; + init_inheritances tenv ; global_tenv := Some tenv ; store_to_filename tenv global_tenv_path + + +let get_summary_formals tenv ~get_summary ~get_formals = + let get pname = + match (get_summary pname, get_formals pname) with + | Some summary, Some formals -> + `Found (summary, formals) + | _, _ -> + `NotFound + in + let found_from_subclass pname = function + | `Found (summary, formals) -> + `FoundFromSubclass (pname, summary, formals) + | v -> + v + in + let rec get_summary_formals_aux pname = + match get pname with + | `Found _ as v -> + v + | `NotFound -> ( + match Procname.get_class_type_name pname with + | None -> + `NotFound + | Some class_name when String.is_prefix (Typ.Name.name class_name) ~prefix:"java." -> + (* Note: We do not search sub-classes of `java.` because the super-classes are too + general. Selecting one arbitrary sub-class of them does not help in making preciser + analysis results. *) + `NotFound + | Some class_name -> ( + match lookup tenv class_name with + | Some {Struct.java_class_kind= Some Interface; subs} + when Int.equal (Typ.Name.Set.cardinal subs) 1 -> + let unique_sub = Typ.Name.Set.choose subs in + Logging.d_printfln_escaped "Found a unique sub-class %a" Typ.Name.pp unique_sub ; + let sub_pname = Procname.replace_class pname unique_sub in + get_summary_formals_aux sub_pname |> found_from_subclass sub_pname + | Some {Struct.java_class_kind= Some AbstractClass; subs} -> + Option.value_map (Typ.Name.Set.min_elt_opt subs) ~default:`NotFound ~f:(fun sub -> + Logging.d_printfln_escaped "Found an arbitrary sub-class %a" Typ.Name.pp sub ; + let sub_pname = Procname.replace_class pname sub in + get sub_pname |> found_from_subclass sub_pname ) + | _ -> + `NotFound ) ) + in + fun pname -> get_summary_formals_aux pname diff --git a/infer/src/IR/Tenv.mli b/infer/src/IR/Tenv.mli index d9e4b5b88..436690b89 100644 --- a/infer/src/IR/Tenv.mli +++ b/infer/src/IR/Tenv.mli @@ -41,6 +41,7 @@ val mk_struct : -> ?exported_objc_methods:Procname.t list -> ?supers:Typ.Name.t list -> ?annots:Annot.Item.t + -> ?java_class_kind:Struct.java_class_kind -> ?dummy:bool -> Typ.Name.t -> Struct.t @@ -65,4 +66,22 @@ val merge_per_file : src:per_file -> dst:per_file -> per_file (** Best-effort merge of [src] into [dst]. If a procedure is both in [dst] and [src], the one in [dst] will get overwritten. *) +val get_summary_formals : + t + -> get_summary:(Procname.t -> 'summary option) + -> get_formals:(Procname.t -> 'formals option) + -> Procname.t + -> [ `NotFound + | `Found of 'summary * 'formals + | `FoundFromSubclass of Procname.t * 'summary * 'formals ] +(** Get summary and formals of the given proc name with heuristics for finding a method in + non-abstract sub-classes + + - If the class is an interface: Find its unique sub-class and apply the heuristics recursively. + + - If the class is an abstract class: Find/use its own summary if possible. If not found, find + one (arbitrary but deterministic) summary from its sub-classes. + + - Otherwise: Find its own summary. *) + module SQLite : SqliteUtils.Data with type t = per_file diff --git a/infer/src/cost/cost.ml b/infer/src/cost/cost.ml index 2961b115a..8a1e8f24a 100644 --- a/infer/src/cost/cost.ml +++ b/infer/src/cost/cost.ml @@ -23,13 +23,12 @@ module Payload = SummaryPayload.Make (struct let field = Payloads.Fields.cost end) -type callee_summary_and_formals = CostDomain.summary * (Pvar.t * Typ.t) list - type extras_WorstCaseCost = { inferbo_invariant_map: BufferOverrunAnalysis.invariant_map ; integer_type_widths: Typ.IntegerWidths.t ; get_node_nb_exec: Node.id -> BasicCost.t - ; get_callee_summary_and_formals: Procname.t -> callee_summary_and_formals option } + ; get_summary: Procname.t -> CostDomain.summary option + ; get_formals: Procname.t -> (Pvar.t * Typ.t) list option } let instantiate_cost integer_type_widths ~inferbo_caller_mem ~callee_pname ~callee_formals ~params ~callee_cost ~loc = @@ -62,7 +61,7 @@ module InstrBasicCost = struct match instr with | Sil.Call (ret, Exp.Const (Const.Cfun callee_pname), params, _, _) when Config.inclusive_cost -> - let {inferbo_invariant_map; integer_type_widths; get_callee_summary_and_formals} = extras in + let {inferbo_invariant_map; integer_type_widths; get_summary; get_formals} = extras in let operation_cost = match BufferOverrunAnalysis.extract_pre (InstrCFG.Node.id instr_node) inferbo_invariant_map @@ -84,12 +83,23 @@ module InstrBasicCost = struct 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) -> + match Tenv.get_summary_formals tenv ~get_summary ~get_formals callee_pname with + | `Found ({CostDomain.post= callee_cost_record}, callee_formals) -> 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 ) - | None -> + | `FoundFromSubclass + (callee_pname, {CostDomain.post= callee_cost_record}, callee_formals) -> + (* Note: It ignores top cost of subclass to avoid its propagations. *) + 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 + if BasicCost.is_top (CostDomain.get_operation_cost instantiated_cost) then + CostDomain.unit_cost_atomic_operation + else instantiated_cost + | `NotFound -> CostDomain.unit_cost_atomic_operation ) ) in if is_allocation_function callee_pname then @@ -316,10 +326,10 @@ 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 tenv integer_type_widths get_callee_summary_and_formals instr_cfg_wto +let compute_worst_case_cost tenv integer_type_widths get_summary get_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} + {inferbo_invariant_map; integer_type_widths; get_node_nb_exec; get_summary; get_formals} in WorstCaseCost.compute tenv extras instr_cfg_wto @@ -366,14 +376,13 @@ let checker {Callbacks.exe_env; summary} : Summary.t = let is_on_ui_thread = ConcurrencyModels.runs_on_ui_thread ~attrs_of_pname tenv proc_name in let get_node_nb_exec = compute_get_node_nb_exec node_cfg bound_map in let astate = - let get_callee_summary_and_formals callee_pname = - Payload.read_full ~caller_summary:summary ~callee_pname - |> Option.map ~f:(fun (callee_pdesc, callee_summary) -> - (callee_summary, Procdesc.get_pvar_formals callee_pdesc) ) + let get_summary callee_pname = Payload.read ~caller_summary:summary ~callee_pname in + let get_formals callee_pname = + Ondemand.get_proc_desc callee_pname |> Option.map ~f:Procdesc.get_pvar_formals in let instr_cfg = InstrCFG.from_pdesc proc_desc in let instr_cfg_wto = InstrCFG.wto instr_cfg in - compute_worst_case_cost tenv integer_type_widths get_callee_summary_and_formals instr_cfg_wto + compute_worst_case_cost tenv integer_type_widths get_summary get_formals instr_cfg_wto inferbo_invariant_map get_node_nb_exec in let () = diff --git a/infer/src/java/jTransType.ml b/infer/src/java/jTransType.ml index fc277e6fd..0d326e57b 100644 --- a/infer/src/java/jTransType.ml +++ b/infer/src/java/jTransType.ml @@ -320,11 +320,11 @@ and get_class_struct_typ = List.iter ~f:(fun cn -> ignore (get_class_struct_typ program tenv cn)) interface_names ; List.map ~f:typename_of_classname interface_names in - let make_struct program tenv node supers ~fields ~statics annots name = + let make_struct program tenv node supers ~fields ~statics annots ~java_class_kind name = let methods = Javalib.m_fold (fun m procnames -> translate_method_name program tenv m :: procnames) node [] in - Tenv.mk_struct tenv ~fields ~statics ~methods ~supers ~annots name + Tenv.mk_struct tenv ~fields ~statics ~methods ~supers ~annots ~java_class_kind name in fun program tenv cn -> let name = typename_of_classname cn in @@ -342,7 +342,8 @@ and get_class_struct_typ = let statics, _ = get_all_fields program tenv cn in let supers = create_super_list program tenv jinterface.Javalib.i_interfaces in let annots = JAnnotation.translate_item jinterface.Javalib.i_annotations in - make_struct program tenv node supers ~fields:[] ~statics annots name + make_struct program tenv node supers ~fields:[] ~statics annots + ~java_class_kind:Struct.Interface name | Some (Javalib.JClass jclass as node) -> let statics, fields = let classpath_static, classpath_nonstatic = get_all_fields program tenv cn in @@ -359,7 +360,10 @@ and get_class_struct_typ = let super_classname = typename_of_classname super_cn in super_classname :: interface_list in - make_struct program tenv node supers ~fields ~statics annots name ) + let java_class_kind = + if jclass.Javalib.c_abstract then Struct.AbstractClass else Struct.NormalClass + in + make_struct program tenv node supers ~fields ~statics annots ~java_class_kind name ) let get_class_type_no_pointer program tenv cn = diff --git a/infer/tests/codetoanalyze/java/performance/InheritanceTest.java b/infer/tests/codetoanalyze/java/performance/InheritanceTest.java new file mode 100644 index 000000000..928e6f41f --- /dev/null +++ b/infer/tests/codetoanalyze/java/performance/InheritanceTest.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +class InheritanceTest { + interface MyInterface { + public void foo(int x); + } + + class UniqueImpl implements MyInterface { + public void foo(int x) { + for (int i = 0; i < x; i++) {} + } + } + + public void call_interface_method_linear(MyInterface c, int x) { + c.foo(x); + } + + interface MyInterface2 { + public void foo(int x); + } + + abstract class AbsImpl implements MyInterface2 { + public abstract void foo(int x); + } + + class Impl1 extends AbsImpl { + @Override + public void foo(int x) { + for (int i = 0; i < x; i++) {} + } + } + + class Impl2 extends AbsImpl { + @Override + public void foo(int x) {} + } + + /* By heuristics, [Impl1.foo] is selected. It is hard to say good or bad. */ + public void call_interface_method2_linear(MyInterface2 c, int x) { + c.foo(x); + } + + interface MyInterface3 { + public int unknown(); + } + + interface MyInterface4 { + public void top_cost(MyInterface3 x); + } + + class UniqueImpl4 implements MyInterface4 { + public void top_cost(MyInterface3 i3) { + for (int i = 0; i < i3.unknown(); i++) {} + } + } + + public void ignore_top_costed_sub_method_constant(MyInterface3 i3, MyInterface4 i4) { + i4.top_cost(i3); + } +} diff --git a/infer/tests/codetoanalyze/java/performance/issues.exp b/infer/tests/codetoanalyze/java/performance/issues.exp index 2cec134b7..cf02f289e 100644 --- a/infer/tests/codetoanalyze/java/performance/issues.exp +++ b/infer/tests/codetoanalyze/java/performance/issues.exp @@ -119,6 +119,12 @@ codetoanalyze/java/performance/Cost_test_deps.java, codetoanalyze.java.performan codetoanalyze/java/performance/Cost_test_deps.java, codetoanalyze.java.performance.Cost_test_deps.two_loops():int, 6, EXPENSIVE_EXECUTION_TIME, no_bucket, ERROR, [with estimated cost 545, O(1), degree = 0] codetoanalyze/java/performance/EvilCfg.java, EvilCfg.foo(int,int,boolean):void, 0, INFINITE_EXECUTION_TIME, no_bucket, ERROR, [Unbounded loop,Loop at line 15] codetoanalyze/java/performance/FieldAccess.java, codetoanalyze.java.performance.FieldAccess.iterate_upto_field_size(codetoanalyze.java.performance.FieldAccess$Test):void, 0, EXPENSIVE_EXECUTION_TIME, no_bucket, ERROR, [with estimated cost 2 + 6 ⋅ test.a, O(test.a), degree = 1,{test.a},Loop at line 16] +codetoanalyze/java/performance/InheritanceTest.java, InheritanceTest$Impl1.foo(int):void, 0, EXPENSIVE_EXECUTION_TIME, no_bucket, ERROR, [with estimated cost 2 + 5 ⋅ x, O(x), degree = 1,{x},Loop at line 34] +codetoanalyze/java/performance/InheritanceTest.java, InheritanceTest$UniqueImpl.foo(int):void, 0, EXPENSIVE_EXECUTION_TIME, no_bucket, ERROR, [with estimated cost 2 + 5 ⋅ x, O(x), degree = 1,{x},Loop at line 15] +codetoanalyze/java/performance/InheritanceTest.java, InheritanceTest$UniqueImpl4.top_cost(InheritanceTest$MyInterface3):void, 0, INFINITE_EXECUTION_TIME, no_bucket, ERROR, [Unbounded loop,Loop at line 58] +codetoanalyze/java/performance/InheritanceTest.java, InheritanceTest$UniqueImpl4.top_cost(InheritanceTest$MyInterface3):void, 0, INTEGER_OVERFLOW_L5, no_bucket, ERROR, [,Assignment,Binary operation: ([0, +oo] + 1):signed32] +codetoanalyze/java/performance/InheritanceTest.java, InheritanceTest.call_interface_method2_linear(InheritanceTest$MyInterface2,int):void, 0, EXPENSIVE_EXECUTION_TIME, no_bucket, ERROR, [with estimated cost 8 + 5 ⋅ x, O(x), degree = 1,{x},call to void InheritanceTest$Impl1.foo(int),Loop at line 34] +codetoanalyze/java/performance/InheritanceTest.java, InheritanceTest.call_interface_method_linear(InheritanceTest$MyInterface,int):void, 0, EXPENSIVE_EXECUTION_TIME, no_bucket, ERROR, [with estimated cost 8 + 5 ⋅ x, O(x), degree = 1,{x},call to void InheritanceTest$UniqueImpl.foo(int),Loop at line 15] codetoanalyze/java/performance/IntTest.java, IntTest.control_var_band_add_constant(int,int):void, 2, INTEGER_OVERFLOW_L5, no_bucket, ERROR, [,Parameter `x`,Binary operation: ([-oo, +oo] + 1):signed32] codetoanalyze/java/performance/IntTest.java, IntTest.control_var_band_add_constant(int,int):void, 4, INTEGER_OVERFLOW_L5, no_bucket, ERROR, [,Parameter `x`,Binary operation: ([-oo, +oo] + 1):signed32] codetoanalyze/java/performance/IntTest.java, IntTest.intValue_linear(java.lang.Integer):void, 0, EXPENSIVE_EXECUTION_TIME, no_bucket, ERROR, [with estimated cost 2 + 5 ⋅ mKBytesToSend + 3 ⋅ (1+max(0, mKBytesToSend)), O(mKBytesToSend), degree = 1,{1+max(0, mKBytesToSend)},Loop at line 9,{mKBytesToSend},Loop at line 9]