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.
253 lines
9.7 KiB
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
|
|
()
|
|
| _ ->
|
|
() )
|
|
| _ ->
|
|
()
|