|
|
|
/*
|
|
|
|
* vim: set ft=rust:
|
|
|
|
* vim: set ft=reason:
|
|
|
|
*
|
|
|
|
* Copyright (c) 2009 - 2013 Monoidics ltd.
|
|
|
|
* Copyright (c) 2013 - 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.
|
|
|
|
*/
|
|
|
|
open! Utils;
|
|
|
|
|
|
|
|
let module L = Logging;
|
|
|
|
|
|
|
|
let module F = Format;
|
|
|
|
|
|
|
|
|
|
|
|
/** data type for the control flow graph */
|
|
|
|
type cfg = {proc_desc_table: Procname.Hash.t Procdesc.t /** Map proc name to procdesc */};
|
|
|
|
|
|
|
|
|
|
|
|
/** create a new empty cfg */
|
|
|
|
let create_cfg () => {proc_desc_table: Procname.Hash.create 16};
|
|
|
|
|
|
|
|
let add_proc_desc cfg pname pdesc => Procname.Hash.add cfg.proc_desc_table pname pdesc;
|
|
|
|
|
|
|
|
let remove_proc_desc cfg pname => Procname.Hash.remove cfg.proc_desc_table pname;
|
|
|
|
|
|
|
|
let iter_proc_desc cfg f => Procname.Hash.iter f cfg.proc_desc_table;
|
|
|
|
|
|
|
|
let find_proc_desc_from_name cfg pname =>
|
|
|
|
try (Some (Procname.Hash.find cfg.proc_desc_table pname)) {
|
|
|
|
| Not_found => None
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/** Create a new procdesc */
|
|
|
|
let create_proc_desc cfg (proc_attributes: ProcAttributes.t) => {
|
|
|
|
let pdesc = Procdesc.from_proc_attributes called_from_cfg::true proc_attributes;
|
|
|
|
add_proc_desc cfg proc_attributes.proc_name pdesc;
|
|
|
|
pdesc
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/** Iterate over all the nodes in the cfg */
|
|
|
|
let iter_all_nodes f cfg => {
|
|
|
|
let do_proc_desc _ (pdesc: Procdesc.t) =>
|
|
|
|
IList.iter (fun node => f pdesc node) (Procdesc.get_nodes pdesc);
|
|
|
|
iter_proc_desc cfg do_proc_desc
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/** Get all the procdescs (defined and declared) */
|
|
|
|
let get_all_procs cfg => {
|
|
|
|
let procs = ref [];
|
|
|
|
let f _ pdesc => procs := [pdesc, ...!procs];
|
|
|
|
iter_proc_desc cfg f;
|
|
|
|
!procs
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/** Get the procedures whose body is defined in this cfg */
|
|
|
|
let get_defined_procs cfg => IList.filter Procdesc.is_defined (get_all_procs cfg);
|
|
|
|
|
|
|
|
|
|
|
|
/** checks whether a cfg is connected or not */
|
|
|
|
let check_cfg_connectedness cfg => {
|
|
|
|
let is_exit_node n =>
|
|
|
|
switch (Procdesc.Node.get_kind n) {
|
|
|
|
| Procdesc.Node.Exit_node _ => true
|
|
|
|
| _ => false
|
|
|
|
};
|
|
|
|
let broken_node n => {
|
|
|
|
let succs = Procdesc.Node.get_succs n;
|
|
|
|
let preds = Procdesc.Node.get_preds n;
|
|
|
|
switch (Procdesc.Node.get_kind n) {
|
|
|
|
| Procdesc.Node.Start_node _ => IList.length succs == 0 || IList.length preds > 0
|
|
|
|
| Procdesc.Node.Exit_node _ => IList.length succs > 0 || IList.length preds == 0
|
|
|
|
| Procdesc.Node.Stmt_node _
|
|
|
|
| Procdesc.Node.Prune_node _
|
|
|
|
| Procdesc.Node.Skip_node _ => IList.length succs == 0 || IList.length preds == 0
|
|
|
|
| Procdesc.Node.Join_node =>
|
|
|
|
/* Join node has the exception that it may be without predecessors
|
|
|
|
and pointing to an exit node */
|
|
|
|
/* if the if brances end with a return */
|
|
|
|
switch succs {
|
|
|
|
| [n'] when is_exit_node n' => false
|
|
|
|
| _ => IList.length preds == 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
let do_pdesc pd => {
|
|
|
|
let pname = Procname.to_string (Procdesc.get_proc_name pd);
|
|
|
|
let nodes = Procdesc.get_nodes pd;
|
|
|
|
let broken = IList.exists broken_node nodes;
|
|
|
|
if broken {
|
|
|
|
L.out "\n ***BROKEN CFG: '%s'\n" pname
|
|
|
|
} else {
|
|
|
|
L.out "\n ***CONNECTED CFG: '%s'\n" pname
|
|
|
|
}
|
|
|
|
};
|
|
|
|
let pdescs = get_all_procs cfg;
|
|
|
|
IList.iter do_pdesc pdescs
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/** Serializer for control flow graphs */
|
|
|
|
let cfg_serializer: Serialization.serializer cfg = Serialization.create_serializer Serialization.cfg_key;
|
|
|
|
|
|
|
|
|
|
|
|
/** Load a cfg from a file */
|
|
|
|
let load_cfg_from_file (filename: DB.filename) :option cfg =>
|
|
|
|
Serialization.from_file cfg_serializer filename;
|
|
|
|
|
|
|
|
|
|
|
|
/** save a copy in the results dir of the source files of procedures defined in the cfg,
|
|
|
|
unless an updated copy already exists */
|
|
|
|
let save_source_files cfg => {
|
|
|
|
let process_proc _ pdesc => {
|
|
|
|
let loc = Procdesc.get_loc pdesc;
|
|
|
|
let source_file = loc.Location.file;
|
|
|
|
let source_file_str = DB.source_file_to_abs_path source_file;
|
|
|
|
let dest_file = DB.source_file_in_resdir source_file;
|
|
|
|
let dest_file_str = DB.filename_to_string dest_file;
|
|
|
|
let needs_copy =
|
|
|
|
Procdesc.is_defined pdesc &&
|
|
|
|
Sys.file_exists source_file_str && (
|
|
|
|
not (Sys.file_exists dest_file_str) ||
|
|
|
|
DB.file_modified_time (DB.filename_from_string source_file_str) >
|
|
|
|
DB.file_modified_time dest_file
|
|
|
|
);
|
|
|
|
if needs_copy {
|
|
|
|
switch (copy_file source_file_str dest_file_str) {
|
|
|
|
| Some _ => ()
|
|
|
|
| None => L.err "Error cannot create copy of source file %s@." source_file_str
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
iter_proc_desc cfg process_proc
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/** Save the .attr files for the procedures in the cfg. */
|
|
|
|
let save_attributes source_file cfg => {
|
|
|
|
let save_proc pdesc => {
|
|
|
|
let attributes = Procdesc.get_attributes pdesc;
|
|
|
|
let loc = attributes.loc;
|
|
|
|
let attributes' = {
|
|
|
|
let loc' =
|
|
|
|
if (Location.equal loc Location.dummy) {
|
|
|
|
{...loc, file: source_file}
|
|
|
|
} else {
|
|
|
|
loc
|
|
|
|
};
|
|
|
|
{...attributes, loc: loc', source_file_captured: source_file}
|
|
|
|
};
|
|
|
|
AttributesTable.store_attributes attributes'
|
|
|
|
};
|
|
|
|
IList.iter save_proc (get_all_procs cfg)
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/** Inline a synthetic (access or bridge) method. */
|
|
|
|
let inline_synthetic_method ret_id etl pdesc loc_call :option Sil.instr => {
|
|
|
|
let modified = ref None;
|
|
|
|
let debug = false;
|
|
|
|
let found instr instr' => {
|
|
|
|
modified := Some instr';
|
|
|
|
if debug {
|
|
|
|
L.stderr "XX inline_synthetic_method found instr: %a@." (Sil.pp_instr pe_text) instr;
|
|
|
|
L.stderr "XX inline_synthetic_method instr': %a@." (Sil.pp_instr pe_text) instr'
|
|
|
|
}
|
|
|
|
};
|
|
|
|
let do_instr _ instr =>
|
|
|
|
switch (instr, ret_id, etl) {
|
|
|
|
| (
|
|
|
|
Sil.Load _ (Exp.Lfield (Exp.Var _) fn ft) bt _,
|
|
|
|
Some (ret_id, _),
|
|
|
|
[(e1, _)] /* getter for fields */
|
|
|
|
) =>
|
|
|
|
let instr' = Sil.Load ret_id (Exp.Lfield e1 fn ft) bt loc_call;
|
|
|
|
found instr instr'
|
|
|
|
| (Sil.Load _ (Exp.Lfield (Exp.Lvar pvar) fn ft) bt _, Some (ret_id, _), [])
|
|
|
|
when Pvar.is_global pvar =>
|
|
|
|
/* getter for static fields */
|
|
|
|
let instr' = Sil.Load ret_id (Exp.Lfield (Exp.Lvar pvar) fn ft) bt loc_call;
|
|
|
|
found instr instr'
|
|
|
|
| (Sil.Store (Exp.Lfield _ fn ft) bt _ _, _, [(e1, _), (e2, _)] /* setter for fields */) =>
|
|
|
|
let instr' = Sil.Store (Exp.Lfield e1 fn ft) bt e2 loc_call;
|
|
|
|
found instr instr'
|
|
|
|
| (Sil.Store (Exp.Lfield (Exp.Lvar pvar) fn ft) bt _ _, _, [(e1, _)]) when Pvar.is_global pvar =>
|
|
|
|
/* setter for static fields */
|
|
|
|
let instr' = Sil.Store (Exp.Lfield (Exp.Lvar pvar) fn ft) bt e1 loc_call;
|
|
|
|
found instr instr'
|
|
|
|
| (Sil.Call ret_id' (Exp.Const (Const.Cfun pn)) etl' _ cf, _, _)
|
|
|
|
when ret_id == None == (ret_id' == None) && IList.length etl' == IList.length etl =>
|
|
|
|
let instr' = Sil.Call ret_id (Exp.Const (Const.Cfun pn)) etl loc_call cf;
|
|
|
|
found instr instr'
|
|
|
|
| (Sil.Call ret_id' (Exp.Const (Const.Cfun pn)) etl' _ cf, _, _)
|
|
|
|
when ret_id == None == (ret_id' == None) && IList.length etl' + 1 == IList.length etl =>
|
|
|
|
let etl1 =
|
|
|
|
switch (IList.rev etl) {
|
|
|
|
/* remove last element */
|
|
|
|
| [_, ...l] => IList.rev l
|
|
|
|
| [] => assert false
|
|
|
|
};
|
|
|
|
let instr' = Sil.Call ret_id (Exp.Const (Const.Cfun pn)) etl1 loc_call cf;
|
|
|
|
found instr instr'
|
|
|
|
| _ => ()
|
|
|
|
};
|
|
|
|
Procdesc.iter_instrs do_instr pdesc;
|
|
|
|
!modified
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/** Find synthetic (access or bridge) Java methods in the procedure and inline them in the cfg. */
|
|
|
|
let proc_inline_synthetic_methods cfg pdesc :unit => {
|
|
|
|
let instr_inline_synthetic_method =
|
|
|
|
fun
|
|
|
|
| Sil.Call ret_id (Exp.Const (Const.Cfun pn)) etl loc _ =>
|
|
|
|
switch (find_proc_desc_from_name cfg pn) {
|
|
|
|
| Some pd =>
|
|
|
|
let is_access = Procname.java_is_access_method pn;
|
|
|
|
let attributes = Procdesc.get_attributes pd;
|
|
|
|
let is_synthetic = attributes.is_synthetic_method;
|
|
|
|
let is_bridge = attributes.is_bridge_method;
|
|
|
|
if (is_access || is_bridge || is_synthetic) {
|
|
|
|
inline_synthetic_method ret_id etl pd loc
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
| None => None
|
|
|
|
}
|
|
|
|
| _ => None;
|
|
|
|
let node_inline_synthetic_methods node => {
|
|
|
|
let modified = ref false;
|
|
|
|
let do_instr instr =>
|
|
|
|
switch (instr_inline_synthetic_method instr) {
|
|
|
|
| None => instr
|
|
|
|
| Some instr' =>
|
|
|
|
modified := true;
|
|
|
|
instr'
|
|
|
|
};
|
|
|
|
let instrs = Procdesc.Node.get_instrs node;
|
|
|
|
let instrs' = IList.map do_instr instrs;
|
|
|
|
if !modified {
|
|
|
|
Procdesc.Node.replace_instrs node instrs'
|
|
|
|
}
|
|
|
|
};
|
|
|
|
Procdesc.iter_nodes node_inline_synthetic_methods pdesc
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/** Inline the java synthetic methods in the cfg */
|
|
|
|
let inline_java_synthetic_methods cfg => {
|
|
|
|
let f pname pdesc =>
|
|
|
|
if (Procname.is_java pname) {
|
|
|
|
proc_inline_synthetic_methods cfg pdesc
|
|
|
|
};
|
|
|
|
iter_proc_desc cfg f
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/** compute the list of procedures added or changed in [cfg_new] over [cfg_old] */
|
|
|
|
let mark_unchanged_pdescs cfg_new cfg_old => {
|
|
|
|
let pdescs_eq (pd1: Procdesc.t) (pd2: Procdesc.t) => {
|
|
|
|
/* map of exp names in pd1 -> exp names in pd2 */
|
|
|
|
let exp_map = ref Exp.Map.empty;
|
|
|
|
/* map of node id's in pd1 -> node id's in pd2 */
|
|
|
|
let node_map = ref Procdesc.NodeMap.empty;
|
|
|
|
/* formals are the same if their types are the same */
|
|
|
|
let formals_eq formals1 formals2 =>
|
|
|
|
IList.equal (fun (_, typ1) (_, typ2) => Typ.compare typ1 typ2) formals1 formals2;
|
|
|
|
let nodes_eq n1s n2s => {
|
|
|
|
/* nodes are the same if they have the same id, instructions, and succs/preds up to renaming
|
|
|
|
with [exp_map] and [id_map] */
|
|
|
|
let node_eq (n1: Procdesc.Node.t) (n2: Procdesc.Node.t) => {
|
|
|
|
let id_compare (n1: Procdesc.Node.t) (n2: Procdesc.Node.t) =>
|
|
|
|
try {
|
|
|
|
let n1_mapping = Procdesc.NodeMap.find n1 !node_map;
|
|
|
|
Procdesc.Node.compare n1_mapping n2
|
|
|
|
} {
|
|
|
|
| Not_found =>
|
|
|
|
/* assume id's are equal and enforce by adding to [id_map] */
|
|
|
|
node_map := Procdesc.NodeMap.add n1 n2 !node_map;
|
|
|
|
0
|
|
|
|
};
|
|
|
|
let instrs_eq instrs1 instrs2 =>
|
|
|
|
IList.equal
|
|
|
|
(
|
|
|
|
fun i1 i2 => {
|
|
|
|
let (n, exp_map') = Sil.instr_compare_structural i1 i2 !exp_map;
|
|
|
|
exp_map := exp_map';
|
|
|
|
n
|
|
|
|
}
|
|
|
|
)
|
|
|
|
instrs1
|
|
|
|
instrs2;
|
|
|
|
id_compare n1 n2 == 0 &&
|
|
|
|
IList.equal Procdesc.Node.compare (Procdesc.Node.get_succs n1) (Procdesc.Node.get_succs n2) &&
|
|
|
|
IList.equal Procdesc.Node.compare (Procdesc.Node.get_preds n1) (Procdesc.Node.get_preds n2) &&
|
|
|
|
instrs_eq (Procdesc.Node.get_instrs n1) (Procdesc.Node.get_instrs n2)
|
|
|
|
};
|
|
|
|
try (IList.for_all2 node_eq n1s n2s) {
|
|
|
|
| Invalid_argument _ => false
|
|
|
|
}
|
|
|
|
};
|
|
|
|
let att1 = Procdesc.get_attributes pd1
|
|
|
|
and att2 = Procdesc.get_attributes pd2;
|
|
|
|
att1.is_defined == att2.is_defined &&
|
|
|
|
Typ.equal att1.ret_type att2.ret_type &&
|
|
|
|
formals_eq att1.formals att2.formals &&
|
|
|
|
nodes_eq (Procdesc.get_nodes pd1) (Procdesc.get_nodes pd2)
|
|
|
|
};
|
|
|
|
let old_procs = cfg_old.proc_desc_table;
|
|
|
|
let new_procs = cfg_new.proc_desc_table;
|
|
|
|
let mark_pdesc_if_unchanged pname (new_pdesc: Procdesc.t) =>
|
|
|
|
try {
|
|
|
|
let old_pdesc = Procname.Hash.find old_procs pname;
|
|
|
|
let changed =
|
|
|
|
/* in continue_capture mode keep the old changed bit */
|
|
|
|
Config.continue_capture && (Procdesc.get_attributes old_pdesc).changed ||
|
|
|
|
not (pdescs_eq old_pdesc new_pdesc);
|
|
|
|
(Procdesc.get_attributes new_pdesc).changed = changed
|
|
|
|
} {
|
|
|
|
| Not_found => ()
|
|
|
|
};
|
|
|
|
Procname.Hash.iter mark_pdesc_if_unchanged new_procs
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/** Save a cfg into a file */
|
|
|
|
let store_cfg_to_file
|
|
|
|
save_sources::save_sources=true
|
|
|
|
source_file::source_file
|
|
|
|
(filename: DB.filename)
|
|
|
|
(cfg: cfg) => {
|
|
|
|
inline_java_synthetic_methods cfg;
|
|
|
|
if save_sources {
|
|
|
|
save_source_files cfg
|
|
|
|
};
|
|
|
|
if Config.incremental_procs {
|
|
|
|
switch (load_cfg_from_file filename) {
|
|
|
|
| Some old_cfg => mark_unchanged_pdescs cfg old_cfg
|
|
|
|
| None => ()
|
|
|
|
}
|
|
|
|
};
|
|
|
|
save_attributes source_file cfg;
|
|
|
|
Serialization.to_file cfg_serializer filename cfg
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/** clone a procedure description and apply the type substitutions where
|
|
|
|
the parameters are used */
|
|
|
|
let specialize_types_proc callee_pdesc resolved_pdesc substitutions => {
|
|
|
|
let resolved_pname = Procdesc.get_proc_name resolved_pdesc
|
|
|
|
and callee_start_node = Procdesc.get_start_node callee_pdesc
|
|
|
|
and callee_exit_node = Procdesc.get_exit_node callee_pdesc;
|
|
|
|
let convert_pvar pvar => Pvar.mk (Pvar.get_name pvar) resolved_pname;
|
|
|
|
let convert_exp =
|
|
|
|
fun
|
|
|
|
| Exp.Lvar origin_pvar => Exp.Lvar (convert_pvar origin_pvar)
|
|
|
|
| exp => exp;
|
|
|
|
let extract_class_name =
|
|
|
|
fun
|
|
|
|
| Typ.Tptr (Tstruct name) _ => Typename.name name
|
|
|
|
| _ => failwith "Expecting classname for Java types";
|
|
|
|
let subst_map = ref Ident.IdentMap.empty;
|
|
|
|
let redirected_class_name origin_id =>
|
|
|
|
try (Some (Ident.IdentMap.find origin_id !subst_map)) {
|
|
|
|
| Not_found => None
|
|
|
|
};
|
|
|
|
let convert_instr instrs =>
|
|
|
|
fun
|
|
|
|
| Sil.Load id (Exp.Lvar origin_pvar as origin_exp) origin_typ loc => {
|
|
|
|
let (_, specialized_typ) = {
|
|
|
|
let pvar_name = Pvar.get_name origin_pvar;
|
|
|
|
try (IList.find (fun (n, _) => Mangled.equal n pvar_name) substitutions) {
|
|
|
|
| Not_found => (pvar_name, origin_typ)
|
|
|
|
}
|
|
|
|
};
|
|
|
|
subst_map := Ident.IdentMap.add id specialized_typ !subst_map;
|
|
|
|
[Sil.Load id (convert_exp origin_exp) specialized_typ loc, ...instrs]
|
|
|
|
}
|
|
|
|
| Sil.Load id (Exp.Var origin_id as origin_exp) origin_typ loc => {
|
|
|
|
let updated_typ =
|
|
|
|
switch (Ident.IdentMap.find origin_id !subst_map) {
|
|
|
|
| Typ.Tptr typ _ => typ
|
|
|
|
| _ => failwith "Expecting a pointer type"
|
|
|
|
| exception Not_found => origin_typ
|
|
|
|
};
|
|
|
|
[Sil.Load id (convert_exp origin_exp) updated_typ loc, ...instrs]
|
|
|
|
}
|
|
|
|
| Sil.Load id origin_exp origin_typ loc => [
|
|
|
|
Sil.Load id (convert_exp origin_exp) origin_typ loc,
|
|
|
|
...instrs
|
|
|
|
]
|
|
|
|
| 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;
|
|
|
|
[set_instr, ...instrs]
|
|
|
|
}
|
|
|
|
| Sil.Call
|
|
|
|
return_ids
|
|
|
|
(Exp.Const (Const.Cfun (Procname.Java callee_pname_java)))
|
|
|
|
[(Exp.Var id, _), ...origin_args]
|
|
|
|
loc
|
|
|
|
call_flags
|
|
|
|
when call_flags.CallFlags.cf_virtual && redirected_class_name id != None => {
|
|
|
|
let redirected_typ = Option.get (redirected_class_name id);
|
|
|
|
let redirected_pname =
|
|
|
|
Procname.replace_class
|
|
|
|
(Procname.Java callee_pname_java) (extract_class_name redirected_typ)
|
|
|
|
and args = {
|
|
|
|
let other_args = IList.map (fun (exp, typ) => (convert_exp exp, typ)) origin_args;
|
|
|
|
[(Exp.Var id, redirected_typ), ...other_args]
|
|
|
|
};
|
|
|
|
let call_instr =
|
|
|
|
Sil.Call return_ids (Exp.Const (Const.Cfun redirected_pname)) args loc call_flags;
|
|
|
|
[call_instr, ...instrs]
|
|
|
|
}
|
|
|
|
| Sil.Call return_ids origin_call_exp origin_args loc call_flags => {
|
|
|
|
let converted_args = IList.map (fun (exp, typ) => (convert_exp exp, typ)) origin_args;
|
|
|
|
let call_instr =
|
|
|
|
Sil.Call return_ids (convert_exp origin_call_exp) converted_args loc call_flags;
|
|
|
|
[call_instr, ...instrs]
|
|
|
|
}
|
|
|
|
| Sil.Prune origin_exp loc is_true_branch if_kind => [
|
|
|
|
Sil.Prune (convert_exp origin_exp) loc is_true_branch if_kind,
|
|
|
|
...instrs
|
|
|
|
]
|
|
|
|
| Sil.Declare_locals typed_vars loc => {
|
|
|
|
let new_typed_vars = IList.map (fun (pvar, typ) => (convert_pvar pvar, typ)) typed_vars;
|
|
|
|
[Sil.Declare_locals new_typed_vars loc, ...instrs]
|
|
|
|
}
|
|
|
|
| Sil.Nullify _
|
|
|
|
| Abstract _
|
|
|
|
| Sil.Remove_temps _ =>
|
|
|
|
/* these are generated instructions that will be replaced by the preanalysis */
|
|
|
|
instrs;
|
|
|
|
let convert_node_kind =
|
|
|
|
fun
|
|
|
|
| Procdesc.Node.Start_node _ => Procdesc.Node.Start_node resolved_pname
|
|
|
|
| Procdesc.Node.Exit_node _ => Procdesc.Node.Exit_node resolved_pname
|
|
|
|
| node_kind => node_kind;
|
|
|
|
let node_map = ref Procdesc.NodeMap.empty;
|
|
|
|
let rec convert_node node => {
|
|
|
|
let loc = Procdesc.Node.get_loc node
|
|
|
|
and kind = convert_node_kind (Procdesc.Node.get_kind node)
|
|
|
|
and instrs = IList.fold_left convert_instr [] (Procdesc.Node.get_instrs node) |> IList.rev;
|
|
|
|
Procdesc.create_node resolved_pdesc loc kind instrs
|
|
|
|
}
|
|
|
|
and loop callee_nodes =>
|
|
|
|
switch callee_nodes {
|
|
|
|
| [] => []
|
|
|
|
| [node, ...other_node] =>
|
|
|
|
let converted_node =
|
|
|
|
try (Procdesc.NodeMap.find node !node_map) {
|
|
|
|
| Not_found =>
|
|
|
|
let new_node = convert_node node
|
|
|
|
and successors = Procdesc.Node.get_succs node
|
|
|
|
and exn_nodes = Procdesc.Node.get_exn node;
|
|
|
|
node_map := Procdesc.NodeMap.add node new_node !node_map;
|
|
|
|
if (Procdesc.Node.equal node callee_start_node) {
|
|
|
|
Procdesc.set_start_node resolved_pdesc new_node
|
|
|
|
};
|
|
|
|
if (Procdesc.Node.equal node callee_exit_node) {
|
|
|
|
Procdesc.set_exit_node resolved_pdesc new_node
|
|
|
|
};
|
|
|
|
Procdesc.node_set_succs_exn callee_pdesc new_node (loop successors) (loop exn_nodes);
|
|
|
|
new_node
|
|
|
|
};
|
|
|
|
[converted_node, ...loop other_node]
|
|
|
|
};
|
|
|
|
ignore (loop [callee_start_node]);
|
|
|
|
resolved_pdesc
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/** Creates a copy of a procedure description and a list of type substitutions of the form
|
|
|
|
(name, typ) where name is a parameter. The resulting proc desc is isomorphic but
|
|
|
|
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 */
|
|
|
|
let specialize_types callee_pdesc resolved_pname args => {
|
|
|
|
/* TODO (#9333890): This currently only works when the callee is defined in the same file.
|
|
|
|
Add support to search for the callee procedure description in the execution environment */
|
|
|
|
let callee_attributes = Procdesc.get_attributes callee_pdesc;
|
|
|
|
let resolved_formals =
|
|
|
|
IList.fold_left2
|
|
|
|
(fun accu (name, _) (_, arg_typ) => [(name, arg_typ), ...accu])
|
|
|
|
[]
|
|
|
|
callee_attributes.formals
|
|
|
|
args |> IList.rev;
|
|
|
|
let resolved_attributes = {
|
|
|
|
...callee_attributes,
|
|
|
|
formals: resolved_formals,
|
|
|
|
proc_name: resolved_pname
|
|
|
|
};
|
|
|
|
AttributesTable.store_attributes resolved_attributes;
|
|
|
|
let resolved_pdesc = {
|
|
|
|
let tmp_cfg = create_cfg ();
|
|
|
|
create_proc_desc tmp_cfg resolved_attributes
|
|
|
|
};
|
|
|
|
specialize_types_proc callee_pdesc resolved_pdesc resolved_formals
|
|
|
|
};
|