From 794c8677fd39581bb73c18ff35371f07f71e7537 Mon Sep 17 00:00:00 2001 From: Dulma Churchill Date: Fri, 10 Nov 2017 06:28:48 -0800 Subject: [PATCH] [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: 0b6f22e --- infer/src/IR/Cfg.ml | 152 +++++++++++++++++- infer/src/IR/Cfg.mli | 8 + infer/src/IR/Pvar.ml | 8 + infer/src/IR/Pvar.mli | 4 + infer/src/backend/SymExecBlocks.ml | 90 +++++++++++ infer/src/backend/SymExecBlocks.mli | 21 +++ infer/src/backend/symExec.ml | 137 +++++++++------- .../codetoanalyze/objc_getters_setters/A.h | 7 + .../codetoanalyze/objc_getters_setters/A.m | 10 ++ .../codetoanalyze/objc_getters_setters/B.h | 5 +- .../codetoanalyze/objc_getters_setters/B.m | 27 ++++ .../objc_getters_setters/issues.exp | 1 + 12 files changed, 408 insertions(+), 62 deletions(-) create mode 100644 infer/src/backend/SymExecBlocks.ml create mode 100644 infer/src/backend/SymExecBlocks.mli diff --git a/infer/src/IR/Cfg.ml b/infer/src/IR/Cfg.ml index f8244b63b..7dbc861ee 100644 --- a/infer/src/IR/Cfg.ml +++ b/infer/src/IR/Cfg.ml @@ -455,8 +455,158 @@ let specialize_types callee_pdesc resolved_pname args = specialize_types_proc callee_pdesc resolved_pdesc substitutions +let specialize_with_block_args_instrs resolved_pdesc substitutions = + let resolved_pname = Procdesc.get_proc_name resolved_pdesc in + let convert_pvar pvar = Pvar.mk (Pvar.get_name pvar) resolved_pname in + let convert_exp exp = + match exp with + | Exp.Lvar origin_pvar -> + let new_pvar = convert_pvar origin_pvar in + Exp.Lvar new_pvar + | _ -> + exp + in + let convert_instr (instrs, id_map) instr = + let convert_generic_call return_ids exp origin_args loc call_flags = + let converted_args = List.map ~f:(fun (exp, typ) -> (convert_exp exp, typ)) origin_args in + let call_instr = Sil.Call (return_ids, exp, converted_args, loc, call_flags) in + (call_instr :: instrs, id_map) + in + match instr with + | Sil.Load (id, Exp.Lvar block_param, _, _) + when Mangled.Map.mem (Pvar.get_name block_param) substitutions -> + let id_map = Ident.IdentMap.add id (Pvar.get_name block_param) id_map in + (* we don't need the load the block param instruction anymore *) + (instrs, id_map) + | Sil.Load (id, origin_exp, origin_typ, loc) -> + (Sil.Load (id, convert_exp origin_exp, origin_typ, loc) :: instrs, id_map) + | Sil.Store (assignee_exp, origin_typ, origin_exp, loc) -> + let set_instr = + Sil.Store (convert_exp assignee_exp, origin_typ, convert_exp origin_exp, loc) + in + (set_instr :: instrs, id_map) + | Sil.Call (return_ids, Exp.Var id, origin_args, loc, call_flags) -> ( + try + let block_name, extra_formals = + let block_var = Ident.IdentMap.find id id_map in + Mangled.Map.find block_var substitutions + in + (* once we find the block in the map, it means that we need to subsitute it with the + call to the concrete block, and pass the fresh formals as arguments *) + let ids_typs, load_instrs = + let captured_ids_instrs = + List.map extra_formals ~f:(fun (var, typ) -> + let id = Ident.create_fresh Ident.knormal in + let pvar = Pvar.mk var resolved_pname in + ((id, typ), Sil.Load (id, Exp.Lvar pvar, typ, loc)) ) + in + List.unzip captured_ids_instrs + in + let call_instr = + let id_exps = List.map ~f:(fun (id, typ) -> (Exp.Var id, typ)) ids_typs in + let converted_args = + List.map ~f:(fun (exp, typ) -> (convert_exp exp, typ)) origin_args + in + Sil.Call + ( return_ids + , Exp.Const (Const.Cfun block_name) + , id_exps @ converted_args + , loc + , call_flags ) + in + let remove_temps_instrs = + let ids = List.map ~f:(fun (id, _) -> id) ids_typs in + Sil.Remove_temps (ids, loc) + in + let instrs = remove_temps_instrs :: call_instr :: load_instrs @ instrs in + (instrs, id_map) + with Not_found -> convert_generic_call return_ids (Exp.Var id) origin_args loc call_flags ) + | Sil.Call (return_ids, origin_call_exp, origin_args, loc, call_flags) -> + convert_generic_call return_ids origin_call_exp origin_args loc call_flags + | Sil.Prune (origin_exp, loc, is_true_branch, if_kind) -> + (Sil.Prune (convert_exp origin_exp, loc, is_true_branch, if_kind) :: instrs, id_map) + | Sil.Declare_locals (typed_vars, loc) -> + let new_typed_vars = + List.map ~f:(fun (pvar, typ) -> (convert_pvar pvar, typ)) typed_vars + in + (Sil.Declare_locals (new_typed_vars, loc) :: instrs, id_map) + | Sil.Nullify _ | Abstract _ | Sil.Remove_temps _ -> + (* these are generated instructions that will be replaced by the preanalysis *) + (instrs, id_map) + in + let convert_instr_list instrs = + let instrs, _ = List.fold ~f:convert_instr ~init:([], Ident.IdentMap.empty) instrs in + List.rev instrs + in + convert_instr_list + + +let specialize_with_block_args callee_pdesc pname_with_block_args block_args = + let callee_attributes = Procdesc.get_attributes callee_pdesc in + (* Substitution from a block parameter to the block name and the new formals + that correspond to the captured variables *) + let substitutions : (Typ.Procname.t * (Mangled.t * Typ.t) list) Mangled.Map.t = + List.fold2_exn callee_attributes.formals block_args ~init:Mangled.Map.empty ~f: + (fun subts (param_name, _) block_arg_opt -> + match block_arg_opt with + | Some (cl: Exp.closure) -> + let formals_from_captured = + List.map + ~f:(fun (_, var, typ) -> + (* Here we create fresh names for the new formals, based on the names of the captured + variables annotated with the name of the caller method *) + (Pvar.get_name_of_local_with_procname var, typ)) + cl.captured_vars + in + Mangled.Map.add param_name (cl.name, formals_from_captured) subts + | None -> + subts ) + in + (* Extend formals with fresh variables for the captured variables of the block arguments, + without duplications. *) + let new_formals_blocks_captured_vars, extended_formals_annots = + let new_formals_blocks_captured_vars_with_annots = + let formals_annots = + List.zip_exn callee_attributes.formals (snd callee_attributes.method_annotation) + in + let append_no_duplicates_formals_and_annot list1 list2 = + IList.append_no_duplicates + (fun ((name1, _), _) ((name2, _), _) -> Mangled.equal name1 name2) + list1 list2 + in + List.fold formals_annots ~init:[] ~f:(fun acc ((param_name, typ), annot) -> + try + let _, captured = Mangled.Map.find param_name substitutions in + append_no_duplicates_formals_and_annot acc + (List.map captured ~f:(fun captured_var -> (captured_var, Annot.Item.empty))) + with Not_found -> append_no_duplicates_formals_and_annot acc [((param_name, typ), annot)] + ) + in + List.unzip new_formals_blocks_captured_vars_with_annots + in + let resolved_attributes = + { callee_attributes with + proc_name= pname_with_block_args + ; is_defined= true + ; err_log= Errlog.empty () + ; source_file_captured= callee_attributes.loc.Location.file + ; formals= new_formals_blocks_captured_vars + ; method_annotation= (fst callee_attributes.method_annotation, extended_formals_annots) } + in + Attributes.store resolved_attributes ; + let resolved_pdesc = + let tmp_cfg = create_cfg () in + create_proc_desc tmp_cfg resolved_attributes + in + Logging.(debug Analysis Verbose) + "signature of base method %a@." Procdesc.pp_signature callee_pdesc ; + Logging.(debug Analysis Verbose) + "signature of specialized method %a@." Procdesc.pp_signature resolved_pdesc ; + convert_cfg ~callee_pdesc ~resolved_pdesc + (specialize_with_block_args_instrs resolved_pdesc substitutions) + + let pp_proc_signatures fmt cfg = F.fprintf fmt "METHOD SIGNATURES@\n@." ; let sorted_procs = List.sort ~cmp:Procdesc.compare (get_all_procs cfg) in List.iter ~f:(fun pdesc -> F.fprintf fmt "%a@." Procdesc.pp_signature pdesc) sorted_procs - diff --git a/infer/src/IR/Cfg.mli b/infer/src/IR/Cfg.mli index 3b4847336..6b604c17a 100644 --- a/infer/src/IR/Cfg.mli +++ b/infer/src/IR/Cfg.mli @@ -56,4 +56,12 @@ val specialize_types : Procdesc.t -> Typ.Procname.t -> (Exp.t * Typ.t) list -> P 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 *) +val specialize_with_block_args : + Procdesc.t -> Typ.Procname.t -> Exp.closure option list -> Procdesc.t +(** Creates a copy of a procedure description given a list of possible closures + that are passed as arguments to the method. The resulting procdesc is isomorphic but + a) the block parameters are replaces with the closures + b) the parameters of the method are extended with parameters for the captured variables + in the closures *) + val pp_proc_signatures : Format.formatter -> cfg -> unit diff --git a/infer/src/IR/Pvar.ml b/infer/src/IR/Pvar.ml index bef228962..45cddb342 100644 --- a/infer/src/IR/Pvar.ml +++ b/infer/src/IR/Pvar.ml @@ -33,6 +33,14 @@ type pvar_kind = (** Names for program variables. *) type t = {pv_hash: int; pv_name: Mangled.t; pv_kind: pvar_kind} [@@deriving compare] +let get_name_of_local_with_procname var = + match var.pv_kind with + | Local_var pname -> + Mangled.from_string (Mangled.to_string var.pv_name ^ "_" ^ Typ.Procname.to_string pname) + | _ -> + var.pv_name + + let compare_modulo_this x y = if phys_equal x y then 0 else diff --git a/infer/src/IR/Pvar.mli b/infer/src/IR/Pvar.mli index a8e4f8c00..4d2937545 100644 --- a/infer/src/IR/Pvar.mli +++ b/infer/src/IR/Pvar.mli @@ -131,3 +131,7 @@ val is_pod : t -> bool val get_initializer_pname : t -> Typ.Procname.t option (** Get the procname of the initializer function for the given global variable *) + +val get_name_of_local_with_procname : t -> Mangled.t +(** [get_name_of_local_with_procname var] Return a name that is composed of the name of +var and the name of the procname in case of locals *) diff --git a/infer/src/backend/SymExecBlocks.ml b/infer/src/backend/SymExecBlocks.ml new file mode 100644 index 000000000..9cb947925 --- /dev/null +++ b/infer/src/backend/SymExecBlocks.ml @@ -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 diff --git a/infer/src/backend/SymExecBlocks.mli b/infer/src/backend/SymExecBlocks.mli new file mode 100644 index 000000000..fddd9a2eb --- /dev/null +++ b/infer/src/backend/SymExecBlocks.mli @@ -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. *) diff --git a/infer/src/backend/symExec.ml b/infer/src/backend/symExec.ml index dc96f5b62..ca1a68a62 100644 --- a/infer/src/backend/symExec.ml +++ b/infer/src/backend/symExec.ml @@ -1259,73 +1259,90 @@ let rec sym_exec tenv current_pdesc _instr (prop_: Prop.normal Prop.t) path | [] -> callee_pname in - let resolved_summary_opt = Ondemand.analyze_proc_name current_pdesc resolved_pname in - let callee_pdesc_opt = Ondemand.get_proc_desc resolved_pname in - let ret_typ_opt = Option.map ~f:Procdesc.get_ret_type callee_pdesc_opt in - let sentinel_result = - if Config.curr_language_is Config.Clang then - check_variadic_sentinel_if_present - (call_args prop_r callee_pname actual_params ret_id loc) - else [(prop_r, path)] + (* 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 current_pdesc resolved_pname + actual_params + else None in - let do_call (prop, path) = - if Option.value_map - ~f:(fun summary -> is_some (reason_to_skip summary)) - ~default:true resolved_summary_opt - then - (* If it's an ObjC getter or setter, call the builtin rather than skipping *) - let attrs_opt = - let attr_opt = Option.map ~f:Procdesc.get_attributes callee_pdesc_opt in - match (attr_opt, resolved_pname) with - | Some attrs, Typ.Procname.ObjC_Cpp _ -> - Some attrs - | None, Typ.Procname.ObjC_Cpp _ -> - Attributes.load resolved_pname - | _ -> - None + match with_block_parameters_summary_opt with + | Some (resolved_summary, extended_actual_params) -> + let prop_r, n_extended_actual_params = + normalize_params tenv current_pname prop_r extended_actual_params in - let objc_property_accessor_ret_typ_opt = - match attrs_opt with - | Some attrs -> ( - match attrs.ProcAttributes.objc_accessor with - | Some objc_accessor -> - Some (objc_accessor, attrs.ProcAttributes.ret_type) - | None -> - None ) - | None -> - None + Logging.d_strln "Calling method specialized with blocks... " ; + proc_call resolved_summary + (call_args prop_r resolved_pname n_extended_actual_params ret_id loc) + | None -> + (* Generic fun call with known name *) + let resolved_summary_opt = Ondemand.analyze_proc_name current_pdesc resolved_pname in + let callee_pdesc_opt = Ondemand.get_proc_desc resolved_pname in + let ret_typ_opt = Option.map ~f:Procdesc.get_ret_type callee_pdesc_opt in + let sentinel_result = + if Config.curr_language_is Config.Clang then + check_variadic_sentinel_if_present + (call_args prop_r callee_pname actual_params ret_id loc) + else [(prop_r, path)] in - match objc_property_accessor_ret_typ_opt with - | Some (objc_property_accessor, ret_typ) -> - handle_objc_instance_method_call n_actual_params n_actual_params prop tenv ret_id - current_pdesc callee_pname loc path - (sym_exec_objc_accessor objc_property_accessor ret_typ) - | None -> - let ret_annots = - match resolved_summary_opt with - | Some summ -> - let ret_annots, _ = - summ.Specs.attributes.ProcAttributes.method_annotation - in - ret_annots - | None -> - load_ret_annots resolved_pname + let do_call (prop, path) = + if Option.value_map + ~f:(fun summary -> is_some (reason_to_skip summary)) + ~default:true resolved_summary_opt + then + (* If it's an ObjC getter or setter, call the builtin rather than skipping *) + let attrs_opt = + let attr_opt = Option.map ~f:Procdesc.get_attributes callee_pdesc_opt in + match (attr_opt, resolved_pname) with + | Some attrs, Typ.Procname.ObjC_Cpp _ -> + Some attrs + | None, Typ.Procname.ObjC_Cpp _ -> + Attributes.load resolved_pname + | _ -> + None in - let is_objc_instance_method = + let objc_property_accessor_ret_typ_opt = match attrs_opt with - | Some attrs -> - attrs.ProcAttributes.is_objc_instance_method + | Some attrs -> ( + match attrs.ProcAttributes.objc_accessor with + | Some objc_accessor -> + Some (objc_accessor, attrs.ProcAttributes.ret_type) + | None -> + None ) | None -> - false + None in - skip_call ~is_objc_instance_method ~reason:"function or method not found" prop - path resolved_pname ret_annots loc ret_id ret_typ_opt n_actual_params - else - proc_call - (Option.value_exn resolved_summary_opt) - (call_args prop resolved_pname n_actual_params ret_id loc) - in - List.concat_map ~f:do_call sentinel_result ) + match objc_property_accessor_ret_typ_opt with + | Some (objc_property_accessor, ret_typ) -> + handle_objc_instance_method_call n_actual_params n_actual_params prop tenv + ret_id current_pdesc callee_pname loc path + (sym_exec_objc_accessor objc_property_accessor ret_typ) + | None -> + let ret_annots = + match resolved_summary_opt with + | Some summ -> + let ret_annots, _ = + summ.Specs.attributes.ProcAttributes.method_annotation + in + ret_annots + | None -> + load_ret_annots resolved_pname + in + let is_objc_instance_method = + match attrs_opt with + | Some attrs -> + attrs.ProcAttributes.is_objc_instance_method + | None -> + false + in + skip_call ~is_objc_instance_method ~reason:"function or method not found" + prop path resolved_pname ret_annots loc ret_id ret_typ_opt n_actual_params + else + proc_call + (Option.value_exn resolved_summary_opt) + (call_args prop resolved_pname n_actual_params ret_id loc) + in + List.concat_map ~f:do_call sentinel_result ) | Sil.Call (ret_id, fun_exp, actual_params, loc, call_flags) -> (* Call via function pointer *) let prop_r, n_actual_params = normalize_params tenv current_pname prop_ actual_params in diff --git a/infer/tests/build_systems/codetoanalyze/objc_getters_setters/A.h b/infer/tests/build_systems/codetoanalyze/objc_getters_setters/A.h index 5501c473d..86ba954bb 100644 --- a/infer/tests/build_systems/codetoanalyze/objc_getters_setters/A.h +++ b/infer/tests/build_systems/codetoanalyze/objc_getters_setters/A.h @@ -11,8 +11,15 @@ @interface A : NSObject +typedef void (^MyBlock)(int x); + @property(nullable, nonatomic, copy) NSData* metadata; - (int)getX; ++ (void)foo:(int)z + and:(_Nonnull MyBlock)block1 + and_also:(_Nonnull MyBlock)block2 + and:(nullable NSString*)name; + @end diff --git a/infer/tests/build_systems/codetoanalyze/objc_getters_setters/A.m b/infer/tests/build_systems/codetoanalyze/objc_getters_setters/A.m index 2a116503b..cfbe0de7b 100644 --- a/infer/tests/build_systems/codetoanalyze/objc_getters_setters/A.m +++ b/infer/tests/build_systems/codetoanalyze/objc_getters_setters/A.m @@ -24,4 +24,14 @@ return _x; } ++ (void)foo:(int)z + and:(_Nonnull MyBlock)block1 + and_also:(_Nonnull MyBlock)block2 + and:(nullable NSString*)name { + block1(22); + int my_var = 11; + block2(33); + int my_other_var = 12; +} + @end diff --git a/infer/tests/build_systems/codetoanalyze/objc_getters_setters/B.h b/infer/tests/build_systems/codetoanalyze/objc_getters_setters/B.h index eb8049e75..1a262da8e 100644 --- a/infer/tests/build_systems/codetoanalyze/objc_getters_setters/B.h +++ b/infer/tests/build_systems/codetoanalyze/objc_getters_setters/B.h @@ -8,6 +8,9 @@ */ #import -@interface B : NSObject +@interface B : NSObject { + int y; + int x; +} @end diff --git a/infer/tests/build_systems/codetoanalyze/objc_getters_setters/B.m b/infer/tests/build_systems/codetoanalyze/objc_getters_setters/B.m index c3979a4f9..4dd72c17c 100644 --- a/infer/tests/build_systems/codetoanalyze/objc_getters_setters/B.m +++ b/infer/tests/build_systems/codetoanalyze/objc_getters_setters/B.m @@ -46,4 +46,31 @@ // infer_field_get_spec } } + +- (int)calling_method_with_block_parameters { + int h = 10; + int z = 10; + [A foo:h + and:^(int i) { + self->x = i; + } + and_also:^(int i) { + self->y = h + z; + } + and:@"Hi"]; + return self->y; +} + ++ (int)calling_method_with_block_parameters_sets_fields_correctly { + B* b = [B new]; + [b calling_method_with_block_parameters]; + if (b->x + b->y == 42) { + int* p = 0; + return *p; // NPE here, because we know that the values x and y + // are set correctly by calling blocks + } else { + int* p = 0; + return *p; // and not here + } +} @end diff --git a/infer/tests/build_systems/objc_getters_setters/issues.exp b/infer/tests/build_systems/objc_getters_setters/issues.exp index c45316009..ef4ca2cc4 100644 --- a/infer/tests/build_systems/objc_getters_setters/issues.exp +++ b/infer/tests/build_systems/objc_getters_setters/issues.exp @@ -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_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]