You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
infer_clone/infer/src/backend/ClosureSubstSpecializedMeth...

253 lines
9.7 KiB

(*
* 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 L = Logging
module PPPVar = struct
type t = Pvar.t [@@deriving compare, equal]
let pp = Pvar.pp Pp.text
end
module VDom = AbstractDomain.Flat (PPPVar)
module BlockIdMap = AbstractDomain.SafeInvertedMap (Ident) (VDom)
module BlockSpec = struct
type t = Procname.t * (Mangled.t * Typ.t) list [@@deriving compare, equal]
let pp fmt (pname, _) = Procname.pp fmt pname
end
module SpecDom = AbstractDomain.Flat (BlockSpec)
module BlockPvarSpecMap = AbstractDomain.SafeInvertedMap (Mangled) (SpecDom)
module Domain = AbstractDomain.Pair (BlockIdMap) (BlockPvarSpecMap)
let eval_instr ((id_to_pvar_map, pvars_to_blocks_map) : Domain.t) instr =
let open Sil in
match instr with
| Load {id; e= Exp.Lvar pvar} ->
(BlockIdMap.add id (VDom.v pvar) id_to_pvar_map, pvars_to_blocks_map)
| Store {e1= Exp.Lvar pvar; e2= Exp.Var id} ->
let pvars_to_blocks_map =
match Option.bind ~f:VDom.get (BlockIdMap.find_opt id id_to_pvar_map) with
| Some block_var ->
Option.value_map
(BlockPvarSpecMap.find_opt (Pvar.get_name block_var) pvars_to_blocks_map)
~default:pvars_to_blocks_map
~f:(fun res -> BlockPvarSpecMap.add (Pvar.get_name pvar) res pvars_to_blocks_map)
| None ->
pvars_to_blocks_map
in
(id_to_pvar_map, pvars_to_blocks_map)
| Load {id} ->
(BlockIdMap.add id VDom.top id_to_pvar_map, pvars_to_blocks_map)
| Call ((id, _), _, _, _, _) ->
(BlockIdMap.add id VDom.top id_to_pvar_map, pvars_to_blocks_map)
| _ ->
(id_to_pvar_map, pvars_to_blocks_map)
module TransferFunctions = struct
module CFG = CFG
module Domain = Domain
type analysis_data = unit
let exec_instr astate _ _node instr = eval_instr astate instr
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 try_keep_original ~default orig new_ ~f = if phys_equal orig new_ then default else f new_
let try_keep_original2 ~default orig1 new1 orig2 new2 ~f =
if phys_equal orig1 new1 && phys_equal orig2 new2 then default else f new1 new2
let exec_pvar pname pvar = Pvar.swap_proc_in_local_pvar pvar pname
let exec_var pname var =
let open Var in
match var with
| LogicalVar _ ->
var
| ProgramVar pvar ->
try_keep_original ~default:var pvar (exec_pvar pname pvar) ~f:of_pvar
let rec exec_exp pname e =
let open Exp in
match e with
| Var _ | Const _ ->
e
| UnOp (unop, e1, typ) ->
try_keep_original ~default:e e1 (exec_exp pname e1) ~f:(fun e1' -> UnOp (unop, e1', typ))
| BinOp (binop, e1, e2) ->
try_keep_original2 ~default:e e1 (exec_exp pname e1) e2 (exec_exp pname e2) ~f:(fun e1' e2' ->
BinOp (binop, e1', e2') )
| Exn e1 ->
try_keep_original ~default:e e1 (exec_exp pname e1) ~f:(fun e1' -> Exn e1')
| Closure {name; captured_vars} ->
let updated = ref false in
let captured_vars =
List.map captured_vars ~f:(fun ((e, pvar, typ, captured_mode) as captured_var) ->
try_keep_original2 ~default:captured_var e (exec_exp pname e) pvar
(exec_pvar pname pvar) ~f:(fun e' pvar' ->
updated := true ;
(e', pvar', typ, captured_mode) ) )
in
if !updated then Closure {name; captured_vars} else e
| Cast (typ, e1) ->
try_keep_original ~default:e e1 (exec_exp pname e1) ~f:(fun e1' -> Cast (typ, e1'))
| Lvar pvar ->
try_keep_original ~default:e pvar (exec_pvar pname pvar) ~f:(fun pvar' -> Lvar pvar')
| Lfield (e1, fn, typ) ->
try_keep_original ~default:e e1 (exec_exp pname e1) ~f:(fun e1' -> Lfield (e1', fn, typ))
| Lindex (e1, e2) ->
try_keep_original2 ~default:e e1 (exec_exp pname e1) e2 (exec_exp pname e2) ~f:(fun e1' e2' ->
Lindex (e1', e2') )
| Sizeof {typ; nbytes; dynamic_length; subtype} ->
Option.value_map dynamic_length ~default:e ~f:(fun dynamic_length ->
try_keep_original ~default:e dynamic_length (exec_exp pname dynamic_length)
~f:(fun dynamic_length' ->
Sizeof {typ; nbytes; dynamic_length= Some dynamic_length'; subtype} ) )
let exec_metadata pname metadata =
let open Sil in
match metadata with
| Abstract _ | CatchEntry _ | Skip | TryEntry _ | TryExit _ ->
metadata
| ExitScope (vars, loc) ->
let updated = ref false in
let vars' =
List.map vars ~f:(fun var ->
try_keep_original ~default:var var (exec_var pname var) ~f:(fun var' ->
updated := true ;
var' ) )
in
if !updated then ExitScope (vars', loc) else metadata
| Nullify (pvar, loc) ->
try_keep_original ~default:metadata pvar (exec_pvar pname pvar) ~f:(fun pvar' ->
Nullify (pvar', loc) )
| VariableLifetimeBegins (pvar, typ, loc) ->
try_keep_original ~default:metadata pvar (exec_pvar pname pvar) ~f:(fun pvar' ->
VariableLifetimeBegins (pvar', typ, loc) )
let exec_args proc_name args =
let updated = ref false in
let args' =
List.map
~f:(fun ((exp, typ) as exp_typ) ->
try_keep_original ~default:exp_typ exp (exec_exp proc_name exp) ~f:(fun exp' ->
updated := true ;
(exp', typ) ) )
args
in
if !updated then args' else args
let exec_instr proc_name (id_to_pvar_map, pvars_to_blocks_map) instr =
let open Sil in
let res =
match instr with
| Load {id; e; root_typ; loc} ->
[ try_keep_original ~default:instr e (exec_exp proc_name e) ~f:(fun e' ->
Load {id; e= e'; root_typ; typ= root_typ; loc} ) ]
| Store {e1; root_typ; typ; e2; loc} ->
[ try_keep_original2 ~default:instr e1 (exec_exp proc_name e1) e2 (exec_exp proc_name e2)
~f:(fun e1' e2' -> Store {e1= e1'; root_typ; typ; e2= e2'; loc}) ]
| Call (ret_id_typ, Var id, origin_args, loc, call_flags) -> (
let converted_args = exec_args proc_name origin_args in
match Option.bind ~f:VDom.get (BlockIdMap.find_opt id id_to_pvar_map) with
| None ->
[instr]
| Some pvar -> (
match
BlockPvarSpecMap.find_opt (Pvar.get_name pvar) pvars_to_blocks_map
|> Option.bind ~f:SpecDom.get
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
L.debug Capture Verbose "substituting specialized method@\n" ;
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) ->
[ try_keep_original2 ~default:instr origin_call_exp (exec_exp proc_name origin_call_exp)
origin_args (exec_args proc_name origin_args)
~f:(fun converted_call_exp converted_args ->
Call (return_ids, converted_call_exp, converted_args, loc, call_flags) ) ]
| Prune (origin_exp, loc, is_true_branch, if_kind) ->
[ try_keep_original ~default:instr origin_exp (exec_exp proc_name origin_exp)
~f:(fun converted_exp -> Prune (converted_exp, loc, is_true_branch, if_kind)) ]
| Metadata metadata ->
[ try_keep_original ~default:instr metadata (exec_metadata proc_name metadata)
~f:(fun metadata' -> Metadata metadata') ]
in
Array.of_list res
let analyze_at_node (map : Analyzer.invariant_map) node : Domain.t =
match Analyzer.InvariantMap.find_opt (Procdesc.Node.get_id node) map with
| Some abstate ->
abstate.pre
| None ->
(BlockIdMap.top, BlockPvarSpecMap.top)
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 pvars_to_blocks_map =
Mangled.Map.map SpecDom.v formals_to_blocks_map
|> Mangled.Map.to_seq |> BlockPvarSpecMap.of_seq
in
let node_cfg = CFG.from_pdesc pdesc in
let invariant_map =
Analyzer.exec_cfg node_cfg () ~initial:(BlockIdMap.empty, pvars_to_blocks_map)
in
let update_context = eval_instr in
CFG.fold_nodes node_cfg ~init:() ~f:(fun _ node ->
let used_ids = Instrs.instrs_get_normal_vars (CFG.instrs node) in
Ident.update_name_generator used_ids ) ;
let replace_instr _node = exec_instr proc_name in
let context_at_node node = analyze_at_node invariant_map node in
let _has_changed : bool =
Procdesc.replace_instrs_by_using_context pdesc ~f:replace_instr ~update_context
~context_at_node
in
()
| _ ->
() )
| _ ->
()