[cost] Find method of interface or abstract class

Summary:
To find a method in non-abstract sub-classes, this diff applies the
same heuristics of inferbo.

* 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.

Reviewed By: ezgicicek

Differential Revision: D20647101

fbshipit-source-id: 2f8f3ff81
master
Sungkeun Cho 5 years ago committed by Facebook GitHub Bot
parent 902514dccd
commit 534149ff52

@ -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}

@ -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 *)

@ -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

@ -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

@ -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 () =

@ -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 =

@ -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);
}
}

@ -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, [<LHS trace>,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, [<LHS trace>,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, [<LHS trace>,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]

Loading…
Cancel
Save