[clang] Executing methods with blocks as parameters by instantiating the parameters with current blocks
Summary: This diff adds a new way of executing blocks when they are passed as parameters to a method. So far we just skipped the block in this case. Now we can execute it. Let's demonstrate with an example. Say we have //foo has a block parameter that it executes in its body foo (Block block) { block();} // bar calls foo with a concrete block bar() { foo (^(){ self->x = 10; }); }; Now, when we call the method foo with a concrete block, we create a copy of foo instantiated with the concrete block, which in itself is translated as a method with a made-up name. The copy of foo will get a name that is foo extended with the name of the block parameter, the call to the block parameter will be replaced to a call to the concrete block, and the captured variables of the concrete block (self in this case), will be added to the formals of the specialized method foo_block_name. This is turned on at the moment for ObjC methods with ObjC blocks as parameters, and called with concrete blocks. Later on we can extend it to other types of methods, and to C++ lambdas, that are handled similarly to blocks. Another extension is to check when the block has been called with nil instead of an actual block, and raise an error in that case. After this diff, we can also model various methods and functions from the standard library that take blocks as parameters, and remove frontend hacks to deal with that. Reviewed By: ddino Differential Revision: D6260792 fbshipit-source-id: 0b6f22emaster
parent
ae21c3e199
commit
794c8677fd
@ -0,0 +1,90 @@
|
|||||||
|
(*
|
||||||
|
* Copyright (c) 2017 - present Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*)
|
||||||
|
(* Given two lists of tuples (exp1, var1, typ1) and (exp2, var2, typ2)
|
||||||
|
append the lists avoiding duplicates, where if the variables exist we check their
|
||||||
|
equality, otherwise we check the equality of the expressions. This is to avoid
|
||||||
|
adding the same captured variable twice. *)
|
||||||
|
let append_no_duplicates_vars list1 list2 =
|
||||||
|
let eq (exp1, var1_opt, _) (exp2, var2_opt, _) =
|
||||||
|
match (var1_opt, var2_opt) with
|
||||||
|
| Some var1, Some var2 ->
|
||||||
|
Pvar.equal var1 var2
|
||||||
|
| None, None ->
|
||||||
|
Exp.equal exp1 exp2
|
||||||
|
| _ ->
|
||||||
|
false
|
||||||
|
in
|
||||||
|
IList.append_no_duplicates eq list1 list2
|
||||||
|
|
||||||
|
|
||||||
|
(* Given a list of actual parameters for a function, replaces the closures with the
|
||||||
|
captured variables, avoiding adding the same captured variable twice. *)
|
||||||
|
let get_extended_args_for_method_with_block_analysis act_params =
|
||||||
|
let ext_actuals = List.map ~f:(fun (exp, typ) -> (exp, None, typ)) act_params in
|
||||||
|
let args_and_captured =
|
||||||
|
List.fold ext_actuals ~init:[] ~f:(fun all_args act_param ->
|
||||||
|
match act_param with
|
||||||
|
| Exp.Closure cl, _, _ ->
|
||||||
|
let captured =
|
||||||
|
List.map ~f:(fun (exp, var, typ) -> (exp, Some var, typ)) cl.captured_vars
|
||||||
|
in
|
||||||
|
append_no_duplicates_vars all_args captured
|
||||||
|
| _ ->
|
||||||
|
append_no_duplicates_vars all_args [act_param] )
|
||||||
|
in
|
||||||
|
List.map ~f:(fun (exp, _, typ) -> (exp, typ)) args_and_captured
|
||||||
|
|
||||||
|
|
||||||
|
let resolve_method_with_block_args_and_analyze caller_pdesc pname act_params =
|
||||||
|
match Ondemand.get_proc_desc pname with
|
||||||
|
| Some pdesc
|
||||||
|
when Procdesc.is_defined pdesc
|
||||||
|
&& Int.equal (List.length (Procdesc.get_formals pdesc)) (List.length act_params)
|
||||||
|
(* only specialize defined methods, and when formals and actuals have the same length *)
|
||||||
|
-> (
|
||||||
|
(* a list with the same length of the actual params of the function,
|
||||||
|
containing either a Closure or None. *)
|
||||||
|
let block_args =
|
||||||
|
List.map act_params ~f:(function
|
||||||
|
| Exp.Closure cl, _ when Typ.Procname.is_objc_block cl.name ->
|
||||||
|
Some cl
|
||||||
|
| _ ->
|
||||||
|
None )
|
||||||
|
in
|
||||||
|
(* name for the specialized method instantiated with block arguments *)
|
||||||
|
let pname_with_block_args =
|
||||||
|
let block_name_args =
|
||||||
|
List.filter_map block_args ~f:(function
|
||||||
|
| Some (cl: Exp.closure) ->
|
||||||
|
Some (Typ.Procname.block_name_of_procname cl.name)
|
||||||
|
| None ->
|
||||||
|
None )
|
||||||
|
in
|
||||||
|
Typ.Procname.with_block_parameters pname block_name_args
|
||||||
|
in
|
||||||
|
(* new procdesc cloned from the original one, where the block parameters have been
|
||||||
|
replaced by the block arguments. The formals have also been expanded with the captured variables *)
|
||||||
|
let specialized_pdesc =
|
||||||
|
Cfg.specialize_with_block_args pdesc pname_with_block_args block_args
|
||||||
|
in
|
||||||
|
Logging.(debug Analysis Verbose) "Instructions of specialized method:@." ;
|
||||||
|
Procdesc.iter_instrs
|
||||||
|
(fun _ instr -> Logging.(debug Analysis Verbose) "%a@." (Sil.pp_instr Pp.text) instr)
|
||||||
|
specialized_pdesc ;
|
||||||
|
Logging.(debug Analysis Verbose) "End of instructions@." ;
|
||||||
|
match Ondemand.analyze_proc_desc caller_pdesc specialized_pdesc with
|
||||||
|
| Some summary ->
|
||||||
|
(* Since the closures in the formals were replaced by the captured variables,
|
||||||
|
we do the same with the actual arguments *)
|
||||||
|
let extended_args = get_extended_args_for_method_with_block_analysis act_params in
|
||||||
|
Some (summary, extended_args)
|
||||||
|
| None ->
|
||||||
|
None )
|
||||||
|
| _ ->
|
||||||
|
None
|
@ -0,0 +1,21 @@
|
|||||||
|
(*
|
||||||
|
* Copyright (c) 2017 - present Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*)
|
||||||
|
|
||||||
|
val resolve_method_with_block_args_and_analyze :
|
||||||
|
Procdesc.t -> Typ.Procname.t -> (Exp.t * Typ.t) list
|
||||||
|
-> (Specs.summary * (Exp.t * Typ.t) list) option
|
||||||
|
(* [resolve_method_with_block_args_and_analyze caller_pdesc pname args]
|
||||||
|
create a copy of the method pname if it is defined and it's called with
|
||||||
|
the correct number of arguments, and some arguments are block closures.
|
||||||
|
The copy is created by adding extra formals for each captured variable,
|
||||||
|
and by swapping the calls to the block arguments to the calls to the concrete
|
||||||
|
blocks.
|
||||||
|
The new procedure is analyzed and the possibly computed summary is returned
|
||||||
|
together with the list of arguments where the closures where swapped by their
|
||||||
|
captured variables. *)
|
@ -1,3 +1,4 @@
|
|||||||
|
build_systems/codetoanalyze/objc_getters_setters/B.m, B_calling_method_with_block_parameters_sets_fields_correctly, 5, NULL_DEREFERENCE, [start of procedure calling_method_with_block_parameters_sets_fields_correctly,start of procedure calling_method_with_block_parameters,start of procedure foo:and:and_also:and:,start of procedure block,return from a call to objc_blockB_calling_method_with_block_parameters_1,start of procedure block,return from a call to objc_blockB_calling_method_with_block_parameters_2,return from a call to A_foo:and:and_also:and:_objc_blockB_calling_method_with_block_parameters_1_objc_blockB_calling_method_with_block_parameters_2,return from a call to B_calling_method_with_block_parameters,Condition is true]
|
||||||
build_systems/codetoanalyze/objc_getters_setters/B.m, B_npe_no_bad_footprint_in_getter:, 3, NULL_DEREFERENCE, [start of procedure npe_no_bad_footprint_in_getter:]
|
build_systems/codetoanalyze/objc_getters_setters/B.m, B_npe_no_bad_footprint_in_getter:, 3, NULL_DEREFERENCE, [start of procedure npe_no_bad_footprint_in_getter:]
|
||||||
build_systems/codetoanalyze/objc_getters_setters/B.m, B_npe_no_bad_footprint_in_setter:andMetadata:, 3, NULL_DEREFERENCE, [start of procedure npe_no_bad_footprint_in_setter:andMetadata:]
|
build_systems/codetoanalyze/objc_getters_setters/B.m, B_npe_no_bad_footprint_in_setter:andMetadata:, 3, NULL_DEREFERENCE, [start of procedure npe_no_bad_footprint_in_setter:andMetadata:]
|
||||||
build_systems/codetoanalyze/objc_getters_setters/B.m, B_npe_no_precondition_not_met:, 4, NULL_DEREFERENCE, [start of procedure npe_no_precondition_not_met:,start of procedure infer_field_get_spec:,start of procedure withMetadata:,return from a call to A_withMetadata:,return from a call to B_infer_field_get_spec:,start of procedure getX,return from a call to A_getX,Condition is true]
|
build_systems/codetoanalyze/objc_getters_setters/B.m, B_npe_no_precondition_not_met:, 4, NULL_DEREFERENCE, [start of procedure npe_no_precondition_not_met:,start of procedure infer_field_get_spec:,start of procedure withMetadata:,return from a call to A_withMetadata:,return from a call to B_infer_field_get_spec:,start of procedure getX,return from a call to A_getX,Condition is true]
|
||||||
|
Loading…
Reference in new issue