diff --git a/infer/src/IR/Procdesc.ml b/infer/src/IR/Procdesc.ml index 255f48326..a0448cdbb 100644 --- a/infer/src/IR/Procdesc.ml +++ b/infer/src/IR/Procdesc.ml @@ -641,6 +641,15 @@ let create_node pdesc loc kind instrs = create_node_from_not_reversed pdesc loc kind (Instrs.of_list instrs) +let shallow_copy_code_from_pdesc ~orig_pdesc ~dest_pdesc = + dest_pdesc.nodes <- orig_pdesc.nodes ; + dest_pdesc.nodes_num <- orig_pdesc.nodes_num ; + dest_pdesc.start_node <- orig_pdesc.start_node ; + dest_pdesc.exit_node <- orig_pdesc.exit_node ; + dest_pdesc.loop_heads <- orig_pdesc.loop_heads ; + dest_pdesc.wto <- orig_pdesc.wto + + (** Set the successor and exception nodes. If this is a join node right before the exit node, add an extra node in the middle, otherwise nullify and abstract instructions cannot be added after a conditional. *) diff --git a/infer/src/IR/Procdesc.mli b/infer/src/IR/Procdesc.mli index dc6dbbb72..d876e4a52 100644 --- a/infer/src/IR/Procdesc.mli +++ b/infer/src/IR/Procdesc.mli @@ -322,6 +322,8 @@ val is_captured_var : t -> Var.t -> bool val has_modify_in_block_attr : t -> Pvar.t -> bool +val shallow_copy_code_from_pdesc : orig_pdesc:t -> dest_pdesc:t -> unit + (** per-procedure CFGs are stored in the SQLite "procedures" table as NULL if the procedure has no CFG *) module SQLite : SqliteUtils.Data with type t = t option diff --git a/infer/src/IR/Pvar.ml b/infer/src/IR/Pvar.ml index e4bfab0d3..308097782 100644 --- a/infer/src/IR/Pvar.ml +++ b/infer/src/IR/Pvar.ml @@ -308,6 +308,10 @@ let get_initializer_pname {pv_name; pv_kind} = None +let swap_proc_in_local_pvar pvar proc_name = + match pvar.pv_kind with Local_var _ -> {pvar with pv_kind= Local_var proc_name} | _ -> pvar + + let rename ~f {pv_name; pv_kind} = let pv_name = Mangled.rename ~f pv_name in let pv_hash = name_hash pv_name in diff --git a/infer/src/IR/Pvar.mli b/infer/src/IR/Pvar.mli index 537fc5a9d..a214483c8 100644 --- a/infer/src/IR/Pvar.mli +++ b/infer/src/IR/Pvar.mli @@ -167,6 +167,8 @@ val build_formal_from_pvar : t -> Mangled.t val materialized_cpp_temporary : string +val swap_proc_in_local_pvar : t -> Procname.t -> t + val rename : f:(string -> string) -> t -> t (** Sets of pvars. *) diff --git a/infer/src/backend/ClosureSubstSpecializedMethod.ml b/infer/src/backend/ClosureSubstSpecializedMethod.ml new file mode 100644 index 000000000..fdfd84afd --- /dev/null +++ b/infer/src/backend/ClosureSubstSpecializedMethod.ml @@ -0,0 +1,114 @@ +(* + * 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. + *) +open! IStd +module CFG = ProcCfg.Normal + +module PPPVar = struct + type t = Pvar.t [@@deriving compare, equal] + + let pp = Pvar.pp Pp.text +end + +module VDom = AbstractDomain.Flat (PPPVar) +module Domain = AbstractDomain.Map (Ident) (VDom) + +module TransferFunctions = struct + module CFG = CFG + module Domain = Domain + + type analysis_data = unit + + let exec_instr astate _analysis_data _node instr = + let open Sil in + match instr with + | Load {id; e= Exp.Lvar pvar} -> + Domain.add id (VDom.v pvar) astate + | Load {id} -> + Domain.add id VDom.bottom astate + | Call ((id, _), _, _, _, _) -> + Domain.add id VDom.bottom astate + | _ -> + astate + + + let pp_session_name node fmt = + Format.fprintf fmt "Closure Subst Specialized Method %a" CFG.Node.pp_id (CFG.Node.id node) +end + +module Analyzer = AbstractInterpreter.MakeRPO (TransferFunctions) + +let exec_instr domain proc_name formals_to_blocks_map _node instr = + let open Sil in + let res = + let exec_exp exp = + let exec_pvar pvar = Pvar.swap_proc_in_local_pvar pvar proc_name in + match exp with Exp.Lvar origin_pvar -> Exp.Lvar (exec_pvar origin_pvar) | exp -> exp + in + match instr with + | Load {id; e; root_typ; loc} -> + [Load {id; e= exec_exp e; root_typ; typ= root_typ; loc}] + | Store {e1; root_typ; typ; e2; loc} -> + [Store {e1= exec_exp e1; root_typ; typ; e2= exec_exp e2; loc}] + | Call (ret_id_typ, Var id, origin_args, loc, call_flags) -> ( + let converted_args = List.map ~f:(fun (exp, typ) -> (exec_exp exp, typ)) origin_args in + match Option.bind ~f:VDom.get (Domain.find_opt id domain) with + | None -> + [instr] + | Some pvar -> ( + match Mangled.Map.find_opt (Pvar.get_name pvar) formals_to_blocks_map with + | Some (procname, extra_formals) -> + let extra_args, load_instrs = + List.map + ~f:(fun (name, typ) -> + let e = Exp.Lvar (Pvar.mk name proc_name) in + let id = Ident.create_fresh Ident.knormal in + let load_instr = Load {id; e; root_typ= typ; typ; loc} in + ((Exp.Var id, typ), load_instr) ) + extra_formals + |> List.unzip + in + load_instrs + @ [ Call + (ret_id_typ, Const (Cfun procname), extra_args @ converted_args, loc, call_flags) + ] + | None -> + [instr] ) ) + | Call (return_ids, origin_call_exp, origin_args, loc, call_flags) -> + let converted_args = List.map ~f:(fun (exp, typ) -> (exec_exp exp, typ)) origin_args in + [Call (return_ids, exec_exp origin_call_exp, converted_args, loc, call_flags)] + | Prune (origin_exp, loc, is_true_branch, if_kind) -> + [Prune (exec_exp origin_exp, loc, is_true_branch, if_kind)] + | _ -> + [instr] + in + Array.of_list res + + +let process summary = + let pdesc = Summary.get_proc_desc summary in + let proc_name = Procdesc.get_proc_name pdesc in + let proc_attributes = Procdesc.get_attributes pdesc in + match proc_attributes.ProcAttributes.specialized_with_blocks_info with + | Some spec_with_blocks_info -> ( + match AnalysisCallbacks.get_proc_desc spec_with_blocks_info.orig_proc with + | Some orig_proc_desc -> ( + let formals_to_blocks_map = spec_with_blocks_info.formals_to_procs_and_new_formals in + Procdesc.shallow_copy_code_from_pdesc ~orig_pdesc:orig_proc_desc ~dest_pdesc:pdesc ; + let analysis_data = () in + match Analyzer.compute_post ~initial:Domain.empty analysis_data pdesc with + | Some domain -> + let used_ids = Domain.bindings domain |> List.map ~f:fst in + Ident.update_name_generator used_ids ; + Procdesc.replace_instrs_by pdesc ~f:(exec_instr domain proc_name formals_to_blocks_map) + |> ignore ; + () + | None -> + () ) + | _ -> + () ) + | _ -> + () diff --git a/infer/src/backend/preanal.ml b/infer/src/backend/preanal.ml index 201a8b840..30f4d9aaa 100644 --- a/infer/src/backend/preanal.ml +++ b/infer/src/backend/preanal.ml @@ -378,7 +378,9 @@ let do_preanalysis exe_env pdesc = if Config.function_pointer_specialization && not (Procname.is_java proc_name) then FunctionPointerSubstitution.process pdesc ; (* NOTE: It is important that this preanalysis stays before Liveness *) - if not (Procname.is_java proc_name) then ClosuresSubstitution.process summary ; + if not (Procname.is_java proc_name) then ( + ClosuresSubstitution.process summary ; + ClosureSubstSpecializedMethod.process summary ) ; Liveness.process summary tenv ; AddAbstractionInstructions.process pdesc ; if Procname.is_java proc_name then Devirtualizer.process summary tenv ; diff --git a/infer/src/checkers/uninit.ml b/infer/src/checkers/uninit.ml index 9f26857a0..a0fe392bb 100644 --- a/infer/src/checkers/uninit.ml +++ b/infer/src/checkers/uninit.ml @@ -95,6 +95,7 @@ module TransferFunctions (CFG : ProcCfg.S) = struct | Some typ, (Var.ProgramVar pv, _) -> (not (Pvar.is_frontend_tmp pv)) && (not (Procdesc.is_captured_pvar pdesc pv)) + && (not (Procdesc.has_modify_in_block_attr pdesc pv)) && MaybeUninitVars.mem access_expr maybe_uninit_vars && should_report_on_type typ | _, _ -> diff --git a/infer/tests/codetoanalyze/objc/pulse/NPEBlocks.m b/infer/tests/codetoanalyze/objc/pulse/NPEBlocks.m index 80b5d5835..b9020338a 100644 --- a/infer/tests/codetoanalyze/objc/pulse/NPEBlocks.m +++ b/infer/tests/codetoanalyze/objc/pulse/NPEBlocks.m @@ -13,6 +13,10 @@ @end +typedef void (^MyBlock)(); + +void dispatch(MyBlock block) { block(); } + @implementation Singleton // Common FP in Pulse NPEs, this requires block specialization @@ -25,6 +29,16 @@ return a->_x; } +- (int)dispatch_no_npe_good { + static Singleton* a = nil; + static dispatch_once_t onceToken; + dispatch(^{ + a = [[Singleton alloc] init]; + a->_x = 5; + }); + return a->_x; +} + @end int captured_npe_bad() { diff --git a/infer/tests/codetoanalyze/objc/self-in-block/NoescapeBlock.m b/infer/tests/codetoanalyze/objc/self-in-block/NoescapeBlock.m index b7d1e1391..3dcfdd828 100644 --- a/infer/tests/codetoanalyze/objc/self-in-block/NoescapeBlock.m +++ b/infer/tests/codetoanalyze/objc/self-in-block/NoescapeBlock.m @@ -74,12 +74,12 @@ if (result != nil) { [resultsList addObject:result]; } - [ArrayUtils enumerate:^(id obj, NSUInteger idx, BOOL* stop) { - __strong __typeof(weakSelf) strongSelf = weakSelf; // no bug here - if (strongSelf) { - int x = strongSelf->x; - } - }]; + }]; + [ArrayUtils enumerate:^(id obj, NSUInteger idx, BOOL* stop) { + __strong __typeof(weakSelf) strongSelf = weakSelf; // no bug here + if (strongSelf) { + int x = strongSelf->x; + } }]; return resultsList; }