From 28200b87d70b7843ae7555b6f572a96d74f502bd Mon Sep 17 00:00:00 2001 From: Dulma Churchill Date: Wed, 25 Apr 2018 06:48:10 -0700 Subject: [PATCH] [objc blocks] Specialize store instructions with current closure in methods specialized with blocks as arguments Reviewed By: mbouaziz, jvillard Differential Revision: D7744403 fbshipit-source-id: 42ad5f8 --- infer/src/IR/Procdesc.ml | 43 ++++++++------- .../tests/codetoanalyze/objc/errors/Makefile | 1 + .../codetoanalyze/objc/errors/issues.exp | 1 + .../RetainCycleBlockAsParameter.m | 54 +++++++++++++++++++ 4 files changed, 80 insertions(+), 19 deletions(-) create mode 100644 infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/RetainCycleBlockAsParameter.m diff --git a/infer/src/IR/Procdesc.ml b/infer/src/IR/Procdesc.ml index b3410acfa..cf69c30ec 100644 --- a/infer/src/IR/Procdesc.ml +++ b/infer/src/IR/Procdesc.ml @@ -662,6 +662,18 @@ let specialize_with_block_args_instrs resolved_pdesc substitutions = exp in let convert_instr (instrs, id_map) instr = + let get_block_name_and_load_captured_vars_instrs block_var loc = + let block_name, extra_formals = Mangled.Map.find block_var substitutions in + let ids, id_exp_typs, load_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, (Exp.Var id, pvar, typ), Sil.Load (id, Exp.Lvar pvar, typ, loc)) ) + |> List.unzip3 + in + let remove_temps_instr = Sil.Remove_temps (ids, loc) in + (block_name, id_exp_typs, load_instrs, remove_temps_instr) + in 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 @@ -675,6 +687,14 @@ let specialize_with_block_args_instrs resolved_pdesc substitutions = (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, Exp.Var id, loc) when Ident.Map.mem id id_map -> + let block_param = Ident.Map.find id id_map in + let block_name, id_exp_typs, load_instrs, remove_temps_instr = + get_block_name_and_load_captured_vars_instrs block_param loc + in + let closure = Exp.Closure {name= block_name; captured_vars= id_exp_typs} in + let instr = Sil.Store (assignee_exp, origin_typ, closure, loc) in + (remove_temps_instr :: instr :: load_instrs @ 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) @@ -682,23 +702,12 @@ let specialize_with_block_args_instrs resolved_pdesc substitutions = (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_name, id_exp_typs, load_instrs, remove_temps_instr = let block_var = Ident.Map.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 + get_block_name_and_load_captured_vars_instrs block_var loc in let call_instr = - let id_exps = List.map ~f:(fun (id, typ) -> (Exp.Var id, typ)) ids_typs in + let id_exps = List.map ~f:(fun (id, _, typ) -> (id, typ)) id_exp_typs in let converted_args = List.map ~f:(fun (exp, typ) -> (convert_exp exp, typ)) origin_args in @@ -709,11 +718,7 @@ let specialize_with_block_args_instrs resolved_pdesc substitutions = , 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 + let instrs = remove_temps_instr :: call_instr :: load_instrs @ instrs in (instrs, id_map) with Caml.Not_found -> convert_generic_call return_ids (Exp.Var id) origin_args loc call_flags ) diff --git a/infer/tests/codetoanalyze/objc/errors/Makefile b/infer/tests/codetoanalyze/objc/errors/Makefile index 30147d9ec..d98c52535 100644 --- a/infer/tests/codetoanalyze/objc/errors/Makefile +++ b/infer/tests/codetoanalyze/objc/errors/Makefile @@ -95,6 +95,7 @@ SOURCES_ARC = \ memory_leaks_benchmark/retain_cycle2.m \ memory_leaks_benchmark/RetainCycleBlocks.m \ memory_leaks_benchmark/RetainCyclePropertyInProtocol.m \ + memory_leaks_benchmark/RetainCycleBlockAsParameter.m \ memory_leaks_benchmark/RetainCycleBlockCapturedVar.m \ memory_leaks_benchmark/RetainCycleDeduplication.m \ npe/BoxedNumberExample.m \ diff --git a/infer/tests/codetoanalyze/objc/errors/issues.exp b/infer/tests/codetoanalyze/objc/errors/issues.exp index ccd418c7c..f21b3cba3 100644 --- a/infer/tests/codetoanalyze/objc/errors/issues.exp +++ b/infer/tests/codetoanalyze/objc/errors/issues.exp @@ -28,6 +28,7 @@ codetoanalyze/objc/errors/field_superclass/SubtypingExample.m, subtyping_test, 0 codetoanalyze/objc/errors/initialization/struct_initlistexpr.c, field_set_correctly, 2, DIVIDE_BY_ZERO, ERROR, [start of procedure field_set_correctly()] codetoanalyze/objc/errors/initialization/struct_initlistexpr.c, implicit_expr_set_correctly, 3, DIVIDE_BY_ZERO, ERROR, [start of procedure implicit_expr_set_correctly()] codetoanalyze/objc/errors/initialization/struct_initlistexpr.c, point_coords_set_correctly, 2, DIVIDE_BY_ZERO, ERROR, [start of procedure point_coords_set_correctly()] +codetoanalyze/objc/errors/memory_leaks_benchmark/RetainCycleBlockAsParameter.m, FBSomeDataManager_fetchNewData, 2, RETAIN_CYCLE, ERROR, [start of procedure fetchNewData,start of procedure initWithCompletionBlock:,return from a call to Fetcher_initWithCompletionBlock:_objc_blockFBSomeDataManager_fetchNewData_1] codetoanalyze/objc/errors/memory_leaks_benchmark/RetainCycleBlockCapturedVar.m, LinkResolver_test_bad, 3, RETAIN_CYCLE, ERROR, [start of procedure test_bad,Executing synthesized setter setDidFinishLoad:] codetoanalyze/objc/errors/memory_leaks_benchmark/RetainCycleBlocks.m, RCBlock_retain_self_in_block, 1, RETAIN_CYCLE, ERROR, [start of procedure retain_self_in_block,Executing synthesized setter setHandler:] codetoanalyze/objc/errors/memory_leaks_benchmark/RetainCycleBlocks.m, objc_blockretain_a_in_block_cycle_3, 1, RETAIN_CYCLE, ERROR, [start of procedure block,Executing synthesized setter setChild:] diff --git a/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/RetainCycleBlockAsParameter.m b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/RetainCycleBlockAsParameter.m new file mode 100644 index 000000000..8224fdbc1 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/RetainCycleBlockAsParameter.m @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018 - 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. + */ +#import +#import + +typedef void (^MyHandler)(NSData* newData); + +@interface Fetcher : NSObject + +@property(nonatomic, strong) MyHandler completionBlock; + +- (instancetype)initWithCompletionBlock:(MyHandler)block; + +@end + +@implementation Fetcher + +- (instancetype)initWithCompletionBlock:(_Nonnull MyHandler)block { + _completionBlock = block; + return self; +} + +@end + +@interface FBSomeDataManager : NSObject + +- (void)setData:(NSData*)data; + +@end + +@implementation FBSomeDataManager { + Fetcher* _fetcher; + NSData* _data; +} + +- (void)setData:(NSData*)data { + _data = data; +} + +- (void)fetchNewData { + // We retain fetcher + _fetcher = [[Fetcher alloc] initWithCompletionBlock:^(NSData* newData) { + // fetcher retains us + [self setData:newData]; + }]; +} + +@end