[biabduction] Enable dynamic dispatch for ObjC methods

Reviewed By: jeremydubreil

Differential Revision: D8451526

fbshipit-source-id: 8fa2a1f
master
Dulma Churchill 7 years ago committed by Facebook Github Bot
parent 2cf0e7a1d2
commit 60cbc2c98e

@ -578,7 +578,7 @@ let specialize_types_proc callee_pdesc resolved_pdesc substitutions =
let resolved_pname = get_proc_name resolved_pdesc in
let convert_pvar pvar = Pvar.mk (Pvar.get_name pvar) resolved_pname in
let mk_ptr_typ typename =
(* Only consider pointers from Java objects for now *)
(* Only consider pointers from objects for now *)
Typ.mk (Tptr (Typ.mk (Tstruct typename), Typ.Pk_pointer))
in
let convert_exp = function
@ -618,16 +618,14 @@ let specialize_types_proc callee_pdesc resolved_pdesc substitutions =
Some set_instr
| Sil.Call
( return_ids
, Exp.Const (Const.Cfun (Typ.Procname.Java callee_pname_java))
, Exp.Const (Const.Cfun callee_pname)
, (Exp.Var id, _) :: origin_args
, loc
, call_flags )
when call_flags.CallFlags.cf_virtual && redirect_typename id <> None ->
let redirected_typename = Option.value_exn (redirect_typename id) in
let redirected_typ = mk_ptr_typ redirected_typename in
let redirected_pname =
Typ.Procname.replace_class (Typ.Procname.Java callee_pname_java) redirected_typename
in
let redirected_pname = Typ.Procname.replace_class callee_pname redirected_typename in
let args =
let other_args = List.map ~f:(fun (exp, typ) -> (convert_exp exp, typ)) origin_args in
(Exp.Var id, redirected_typ) :: other_args
@ -657,29 +655,56 @@ let specialize_types_proc callee_pdesc resolved_pdesc substitutions =
convert_cfg ~callee_pdesc ~resolved_pdesc ~f_instr_list
exception UnmatchedParameters
(** Creates a copy of a procedure description and a list of type substitutions of the form
(name, typ) where name is a parameter. The resulting proc desc is isomorphic but
all the type of the parameters are replaced in the instructions according to the list.
The virtual calls are also replaced to match the parameter types *)
let specialize_types callee_pdesc resolved_pname args =
let specialize_types ?(has_clang_model= false) callee_pdesc resolved_pname args =
let callee_attributes = get_attributes callee_pdesc in
let resolved_params, substitutions =
List.fold2_exn
~f:(fun (params, subts) (param_name, param_typ) (_, arg_typ) ->
match arg_typ.Typ.desc with
| Tptr ({desc= Tstruct typename}, Pk_pointer) ->
(* Replace the type of the parameter by the type of the argument *)
((param_name, arg_typ) :: params, Mangled.Map.add param_name typename subts)
| _ ->
((param_name, param_typ) :: params, subts) )
~init:([], Mangled.Map.empty) callee_attributes.formals args
try
List.fold2_exn
~f:(fun (params, subts) (param_name, param_typ) (_, arg_typ) ->
match arg_typ.Typ.desc with
| Tptr ({desc= Tstruct typename}, Pk_pointer) ->
(* Replace the type of the parameter by the type of the argument *)
((param_name, arg_typ) :: params, Mangled.Map.add param_name typename subts)
| _ ->
((param_name, param_typ) :: params, subts) )
~init:([], Mangled.Map.empty) callee_attributes.formals args
with Invalid_argument _ ->
L.internal_error
"Call mismatch: method %a has %i paramters but is called with %i arguments@."
Typ.Procname.pp resolved_pname
(List.length callee_attributes.formals)
(List.length args) ;
raise UnmatchedParameters
in
let translation_unit =
(* If it is a model, and we are using the procdesc stored in the summary, the default translation unit
won't be useful because we don't store that tenv, so we aim to find the source file of the caller to
use its tenv. *)
if has_clang_model then
let pname = get_proc_name callee_pdesc in
match Attributes.find_file_capturing_procedure pname with
| Some (source_file, _) ->
source_file
| None ->
Logging.die InternalError
"specialize_types should only be called with defined procedures, but we cannot find \
the captured file of procname %a"
Typ.Procname.pp pname
else callee_attributes.translation_unit
in
let resolved_attributes =
{ callee_attributes with
formals= List.rev resolved_params
; proc_name= resolved_pname
; is_specialized= true
; err_log= Errlog.empty () }
; err_log= Errlog.empty ()
; translation_unit }
in
Attributes.store resolved_attributes ;
let resolved_pdesc = from_proc_attributes resolved_attributes in

@ -223,7 +223,9 @@ val is_captured_var : t -> Pvar.t -> bool
val has_modify_in_block_attr : t -> Pvar.t -> bool
val specialize_types : t -> Typ.Procname.t -> (Exp.t * Typ.t) list -> t
exception UnmatchedParameters
val specialize_types : ?has_clang_model:bool -> t -> Typ.Procname.t -> (Exp.t * Typ.t) list -> t
(** Creates a copy of a procedure description and a list of type substitutions of the form
(name, typ) where name is a parameter. The resulting procdesc is isomorphic but
all the type of the parameters are replaced in the instructions according to the list.

@ -951,6 +951,10 @@ module Procname = struct
is_c_function name
let is_objc_method procname =
match procname with ObjC_Cpp name -> ObjC_Cpp.is_objc_method name | _ -> false
let block_name_of_procname procname =
match procname with
| Block block ->

@ -473,6 +473,8 @@ being the name of the struct, [None] means the parameter is of some other type.
val parameter_of_name : t -> Name.t -> Parameter.t
val is_objc_method : t -> bool
(** Hash tables with proc names as keys. *)
module Hash : Caml.Hashtbl.S with type key = t

@ -624,19 +624,28 @@ let resolve_virtual_pname tenv prop actuals callee_pname call_flags : Typ.Procna
(** Resolve the name of the procedure to call based on the type of the arguments *)
let resolve_pname tenv prop args pname call_flags : Typ.Procname.t =
let resolve_pname ~caller_pdesc tenv prop args pname call_flags : Typ.Procname.t =
let resolve_from_args resolved_pname args =
let resolved_parameters = Typ.Procname.get_parameters resolved_pname in
let resolved_params =
List.fold2_exn
~f:(fun accu (arg_exp, _) name ->
match resolve_typename prop arg_exp with
| Some class_name ->
Typ.Procname.parameter_of_name resolved_pname class_name :: accu
| None ->
name :: accu )
~init:[] args
(Typ.Procname.get_parameters resolved_pname)
|> List.rev
try
List.fold2_exn
~f:(fun accu (arg_exp, _) name ->
match resolve_typename prop arg_exp with
| Some class_name ->
Typ.Procname.parameter_of_name resolved_pname class_name :: accu
| None ->
name :: accu )
~init:[] args resolved_parameters
|> List.rev
with Invalid_argument _ ->
let loc = (Procdesc.get_attributes caller_pdesc).loc in
let file = loc.Location.file in
L.internal_error
"Call mismatch: method %a has %i paramters but is called with %i arguments, in %a, %a@."
Typ.Procname.pp pname (List.length resolved_parameters) (List.length args) SourceFile.pp
file Location.pp loc ;
raise Procdesc.UnmatchedParameters
in
Typ.Procname.replace_parameters resolved_params resolved_pname
in
@ -661,22 +670,44 @@ let resolve_pname tenv prop args pname call_flags : Typ.Procname.t =
| args when match_parameters args (* Static call *) ->
(pname, args)
| args ->
L.(die InternalError)
"Call mismatch: method %a has %i paramters but is called with %i arguments@."
Typ.Procname.pp pname (List.length parameters) (List.length args)
let loc = (Procdesc.get_attributes caller_pdesc).loc in
let file = loc.Location.file in
L.internal_error
"Call mismatch: method %a has %i paramters but is called with %i arguments, in %a, %a@."
Typ.Procname.pp pname (List.length parameters) (List.length args) SourceFile.pp file
Location.pp loc ;
raise Procdesc.UnmatchedParameters
in
resolve_from_args resolved_pname other_args
let resolve_args prop args =
List.map
~f:(fun ((arg_exp, arg_typ) as arg) ->
match (resolve_typename prop arg_exp, arg_typ.Typ.desc) with
| Some class_name, Tptr (({desc= Tstruct typename} as inner_typ), p) ->
let resolved_arg_typ =
if Typ.Name.equal class_name typename then arg_typ
else
let struct_typ = {inner_typ with desc= Tstruct class_name} in
({arg_typ with desc= Tptr (struct_typ, p)} : Typ.t)
in
(arg_exp, resolved_arg_typ)
| _ ->
arg )
args
(** Resolve the procedure name and run the analysis of the resolved procedure
if not already analyzed *)
let resolve_and_analyze tenv ~caller_pdesc prop args callee_proc_name call_flags
: Typ.Procname.t * Summary.t option =
let resolve_and_analyze tenv ~caller_pdesc ?(has_clang_model= false) prop args callee_proc_name
call_flags : Typ.Procname.t * (Procdesc.t option * Summary.t option) =
(* TODO (#15748878): Fix conflict with method overloading by encoding in the procedure name
whether the method is defined or generated by the specialization *)
let analyze_ondemand resolved_pname : Summary.t option =
let analyze_ondemand resolved_pname : Procdesc.t option * Summary.t option =
if Typ.Procname.equal resolved_pname callee_proc_name then
Ondemand.analyze_proc_name ~caller_pdesc callee_proc_name
( Ondemand.get_proc_desc callee_proc_name
, Ondemand.analyze_proc_name ~caller_pdesc callee_proc_name )
else
(* Create the type sprecialized procedure description and analyze it directly *)
let analyze specialized_pdesc = Ondemand.analyze_proc_desc ~caller_pdesc specialized_pdesc in
@ -685,14 +716,29 @@ let resolve_and_analyze tenv ~caller_pdesc prop args callee_proc_name call_flags
| Some resolved_proc_desc ->
Some resolved_proc_desc
| None ->
let procdesc_opt =
(* If it is a model, we aim to get the procdesc stored in a summary rather than the
(empty) procdesc stored in the caller's cfg. *)
if has_clang_model then
match Summary.get callee_proc_name with
| Some summary ->
Some (Summary.get_proc_desc summary)
| None ->
Ondemand.get_proc_desc callee_proc_name
else Ondemand.get_proc_desc callee_proc_name
in
Option.map
~f:(fun callee_proc_desc ->
Procdesc.specialize_types callee_proc_desc resolved_pname args )
(Ondemand.get_proc_desc callee_proc_name)
(* It is possible that the types of the arguments are not as precise as the type of the objects
in the heap, so we should update them to get the best results. *)
let resolved_args = resolve_args prop args in
Procdesc.specialize_types ~has_clang_model callee_proc_desc resolved_pname
resolved_args )
procdesc_opt
in
Option.bind resolved_proc_desc_option ~f:analyze
(resolved_proc_desc_option, Option.bind resolved_proc_desc_option ~f:analyze)
in
let resolved_pname = resolve_pname tenv prop args callee_proc_name call_flags in
let resolved_pname = resolve_pname ~caller_pdesc tenv prop args callee_proc_name call_flags in
(resolved_pname, analyze_ondemand resolved_pname)
@ -1049,6 +1095,64 @@ let execute_store ?(report_deref_errors= true) pname pdesc tenv lhs_exp typ rhs_
with Rearrange.ARRAY_ACCESS -> if Int.equal Config.array_level 0 then assert false else [prop_]
let is_variadic_procname callee_pname =
Option.value_map
(Ondemand.get_proc_desc callee_pname)
~f:(fun proc_desc -> (Procdesc.get_attributes proc_desc).ProcAttributes.is_variadic)
~default:false
let resolve_and_analyze_no_dynamic_dispatch current_pdesc tenv prop_r n_actual_params callee_pname
call_flags =
let resolved_pname =
match resolve_virtual_pname tenv prop_r n_actual_params callee_pname call_flags with
| resolved_pname :: _ ->
resolved_pname
| [] ->
callee_pname
in
let resolved_summary_opt =
Ondemand.analyze_proc_name ~caller_pdesc:current_pdesc resolved_pname
in
(resolved_pname, (Ondemand.get_proc_desc resolved_pname, resolved_summary_opt))
let resolve_and_analyze_clang current_pdesc tenv prop_r n_actual_params callee_pname call_flags =
if
Config.dynamic_dispatch && not (is_variadic_procname callee_pname)
&& Typ.Procname.is_objc_method callee_pname
(* to be extended to other methods *)
then
try
let has_clang_model = Summary.has_model callee_pname in
let resolved_pname, (resolved_pdesc_opt, resolved_summary_opt) =
resolve_and_analyze tenv ~caller_pdesc:current_pdesc ~has_clang_model prop_r
n_actual_params callee_pname call_flags
in
(* It could be useful to specialize a model, but also it could cause a failure,
because we don't have the correct fields in the tenv.
In that case, default to the non-specialized spec for the model. *)
let clang_model_specialized_failure =
match resolved_summary_opt with
| Some summary when has_clang_model ->
List.is_empty (Tabulation.get_specs_from_payload summary)
| None ->
true
| _ ->
false
in
if clang_model_specialized_failure then
resolve_and_analyze_no_dynamic_dispatch current_pdesc tenv prop_r n_actual_params
callee_pname call_flags
else (resolved_pname, (resolved_pdesc_opt, resolved_summary_opt))
with Procdesc.UnmatchedParameters ->
resolve_and_analyze_no_dynamic_dispatch current_pdesc tenv prop_r n_actual_params
callee_pname call_flags
else
resolve_and_analyze_no_dynamic_dispatch current_pdesc tenv prop_r n_actual_params callee_pname
call_flags
(** Execute [instr] with a symbolic heap [prop].*)
let rec sym_exec exe_env tenv current_pdesc instr_ (prop_: Prop.normal Prop.t) path
: (Prop.normal Prop.t * Paths.Path.t) list =
@ -1180,7 +1284,7 @@ let rec sym_exec exe_env tenv current_pdesc instr_ (prop_: Prop.normal Prop.t) p
skip_call ~reason norm_prop path skipped_pname ret_annots loc ret_id_typ ret_type
norm_args
in
let resolved_pname, resolved_summary_opt =
let resolved_pname, (_, resolved_summary_opt) =
resolve_and_analyze tenv ~caller_pdesc:current_pdesc norm_prop norm_args callee_pname
call_flags
in
@ -1229,18 +1333,11 @@ let rec sym_exec exe_env tenv current_pdesc instr_ (prop_: Prop.normal Prop.t) p
| _ ->
(* Generic fun call with known name *)
let prop_r, n_actual_params = normalize_params tenv current_pname prop_ actual_params in
let resolved_pname =
match resolve_virtual_pname tenv prop_r n_actual_params callee_pname call_flags with
| resolved_pname :: _ ->
resolved_pname
| [] ->
callee_pname
in
(* method with block parameters *)
let with_block_parameters_summary_opt =
if call_flags.CallFlags.cf_with_block_parameters then
SymExecBlocks.resolve_method_with_block_args_and_analyze ~caller_pdesc:current_pdesc
resolved_pname actual_params
callee_pname actual_params
else None
in
match with_block_parameters_summary_opt with
@ -1250,22 +1347,24 @@ let rec sym_exec exe_env tenv current_pdesc instr_ (prop_: Prop.normal Prop.t) p
in
Logging.d_strln "Calling method specialized with blocks... " ;
proc_call exe_env resolved_summary
(call_args prop_r resolved_pname n_extended_actual_params ret_id_typ loc)
(call_args prop_r callee_pname n_extended_actual_params ret_id_typ loc)
| None ->
(* Generic fun call with known name *)
let resolved_summary_opt =
Ondemand.analyze_proc_name ~caller_pdesc:current_pdesc resolved_pname
let resolved_pname, (resolved_pdesc_opt, resolved_summary_opt) =
resolve_and_analyze_clang current_pdesc tenv prop_r n_actual_params callee_pname
call_flags
in
let callee_pdesc_opt = Ondemand.get_proc_desc resolved_pname in
Logging.d_strln ("Original callee " ^ Typ.Procname.to_unique_id callee_pname) ;
Logging.d_strln ("Resolved callee " ^ Typ.Procname.to_unique_id resolved_pname) ;
let sentinel_result =
if Language.curr_language_is Clang then
check_variadic_sentinel_if_present
(call_args prop_r callee_pname actual_params ret_id_typ loc)
(call_args prop_r resolved_pname actual_params ret_id_typ loc)
else [(prop_r, path)]
in
let do_call (prop, path) =
let callee_desc =
match (resolved_summary_opt, callee_pdesc_opt) with
match (resolved_summary_opt, resolved_pdesc_opt) with
| Some summary, _ ->
`Summary summary
| None, Some pdesc ->
@ -1286,21 +1385,23 @@ let rec sym_exec exe_env tenv current_pdesc instr_ (prop_: Prop.normal Prop.t) p
| None ->
load_ret_annots resolved_pname
in
match callee_pdesc_opt with
| Some callee_pdesc
match resolved_pdesc_opt with
| Some resolved_pdesc
-> (
let attrs = Procdesc.get_attributes callee_pdesc in
let attrs = Procdesc.get_attributes resolved_pdesc in
let ret_type = attrs.ProcAttributes.ret_type in
let model_as_malloc = Objc_models.is_malloc_model ret_type callee_pname in
let model_as_malloc =
Objc_models.is_malloc_model ret_type resolved_pname
in
match (attrs.ProcAttributes.objc_accessor, model_as_malloc) with
| Some objc_accessor, _ ->
(* If it's an ObjC getter or setter, call the builtin rather than skipping *)
handle_objc_instance_method_call n_actual_params n_actual_params prop
tenv (fst ret_id_typ) current_pdesc callee_pname loc path
(sym_exec_objc_accessor callee_pname objc_accessor ret_type)
tenv (fst ret_id_typ) current_pdesc resolved_pname loc path
(sym_exec_objc_accessor resolved_pname objc_accessor ret_type)
| None, true ->
(* If it's an alloc model, call alloc rather than skipping *)
sym_exec_alloc_model exe_env callee_pname ret_type tenv ret_id_typ
sym_exec_alloc_model exe_env resolved_pname ret_type tenv ret_id_typ
current_pdesc loc prop path
| None, false ->
let is_objc_instance_method =

@ -1456,9 +1456,9 @@ let exe_function_call exe_env callee_summary tenv ret_id caller_pdesc callee_pna
let spec_list, formal_params = spec_find_rename trace_call callee_summary in
let nspecs = List.length spec_list in
L.d_strln
(F.sprintf "Found %d specs for function %s" nspecs (Typ.Procname.to_string callee_pname)) ;
(F.sprintf "Found %d specs for function %s" nspecs (Typ.Procname.to_unique_id callee_pname)) ;
L.d_strln
(F.sprintf "START EXECUTING SPECS FOR %s from state" (Typ.Procname.to_string callee_pname)) ;
(F.sprintf "START EXECUTING SPECS FOR %s from state" (Typ.Procname.to_unique_id callee_pname)) ;
Prop.d_prop prop ;
L.d_ln () ;
let exe_one_spec (n, spec) =

@ -37,7 +37,7 @@ let all_checkers =
; { name= "biabduction"
; active= Config.biabduction
; callbacks=
[ (Procedure Interproc.analyze_procedure, Language.Clang)
[ (DynamicDispatch Interproc.analyze_procedure, Language.Clang)
; (DynamicDispatch Interproc.analyze_procedure, Language.Java) ] }
; { name= "buffer overrun"
; active= Config.bufferoverrun && not Config.cost (* Cost analysis already triggers Inferbo *)

@ -27,6 +27,7 @@ SOURCES_DEFAULT = \
memory_leaks_benchmark/RetainReleaseExampleBucketing.m \
memory_leaks_benchmark/CoreVideoExample.m \
memory_leaks_benchmark/RetainCycleLength3.m \
npe/dynamic_dispatch.m \
npe/Fraction.m \
npe/NPD_core_foundation.m \
npe/Npe_with_equal_names.m \

@ -71,6 +71,7 @@ codetoanalyze/objc/errors/subtyping/KindOfClassExample.m, shouldThrowDivideByZer
codetoanalyze/objc/errors/subtyping/KindOfClassExample.m, shouldThrowDivideByZero2, 2, DIVIDE_BY_ZERO, no_bucket, ERROR, [start of procedure shouldThrowDivideByZero2(),start of procedure init,return from a call to Base_init,start of procedure returnsZero2(),Taking false branch,return from a call to returnsZero2]
codetoanalyze/objc/errors/subtyping/KindOfClassExample.m, shouldThrowDivideByZero3, 3, DIVIDE_BY_ZERO, no_bucket, ERROR, [start of procedure shouldThrowDivideByZero3(),start of procedure init,return from a call to Derived_init,Taking true branch]
codetoanalyze/objc/errors/variadic_methods/premature_nil_termination.m, PrematureNilTermA_nilInArrayWithObjects, 5, PREMATURE_NIL_TERMINATION_ARGUMENT, B1, ERROR, [start of procedure nilInArrayWithObjects]
objc/src/CADisplayLink.m, CADisplayLink_displayLinkWithTarget:selector:, 3, Missing_fld, no_bucket, ERROR, [start of procedure displayLinkWithTarget:selector:]
codetoanalyze/objc/errors/memory_leaks_benchmark/CoreVideoExample.m, CoreVideoExample_cvpixelbuffer_not_released_leak, 1, MEMORY_LEAK, no_bucket, ERROR, [start of procedure cvpixelbuffer_not_released_leak]
codetoanalyze/objc/errors/memory_leaks_benchmark/NSData_models_tests.m, NSData_models_tests_macForIV:, 2, MEMORY_LEAK, no_bucket, ERROR, [start of procedure macForIV:]
codetoanalyze/objc/errors/memory_leaks_benchmark/NSString_models_tests.m, StringInitA_hexStringValue, 11, MEMORY_LEAK, no_bucket, ERROR, [start of procedure hexStringValue,Skipping CFStringCreateWithBytesNoCopy(): method has no implementation,Taking false branch]
@ -82,6 +83,7 @@ codetoanalyze/objc/errors/npe/block.m, BlockA_foo, 5, NULL_DEREFERENCE, B1, ERRO
codetoanalyze/objc/errors/npe/block.m, BlockA_foo3:, 3, NULL_DEREFERENCE, B1, ERROR, [start of procedure foo3:]
codetoanalyze/objc/errors/npe/block.m, BlockA_foo4:, 6, NULL_DEREFERENCE, B1, ERROR, [start of procedure foo4:]
codetoanalyze/objc/errors/npe/block.m, BlockA_foo7, 2, IVAR_NOT_NULL_CHECKED, B1, WARNING, [start of procedure foo7]
codetoanalyze/objc/errors/npe/dynamic_dispatch.m, DynamicDispatchMain_npe_bad, 2, NULL_DEREFERENCE, B5, ERROR, [start of procedure npe_bad,start of procedure get_ddclass_from:,start of procedure get_ddclass,return from a call to PInstance_get_ddclass,return from a call to DynamicDispatchMain_get_ddclass_from:]
codetoanalyze/objc/errors/npe/ivar_blocks.m, MyClass_ivar_npe, 1, IVAR_NOT_NULL_CHECKED, B1, WARNING, [start of procedure ivar_npe]
codetoanalyze/objc/errors/npe/skip_method_with_nil_object.m, SkipMethodNilA_testBug:, 6, PARAMETER_NOT_NULL_CHECKED, B2, WARNING, [start of procedure testBug:,Message get_a with receiver nil returns nil.,Message skip_method with receiver nil returns nil.,Taking false branch]
codetoanalyze/objc/errors/property/main.c, property_main, 3, MEMORY_LEAK, no_bucket, ERROR, [start of procedure property_main(),Skipping aProperty: method has no implementation]

@ -0,0 +1,52 @@
/*
* Copyright (c) 2018-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 <Foundation/NSObject.h>
@interface DynamicDispatchClass : NSObject
@end
@implementation DynamicDispatchClass {
@public
int x;
}
@end
@protocol P
- (DynamicDispatchClass*)get_ddclass;
@end
@interface PInstance : NSObject<P>
@end
@implementation PInstance
- (DynamicDispatchClass*)get_ddclass {
return nil;
}
@end
@interface DynamicDispatchMain : NSObject
@end
@implementation DynamicDispatchMain
- (DynamicDispatchClass*)get_ddclass_from:(id<P>)object {
return [object get_ddclass];
}
- (int)npe_bad {
PInstance* object = [PInstance new];
return [self get_ddclass_from:object] -> x;
}
@end
Loading…
Cancel
Save