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.

1046 lines
39 KiB

(*
* 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.
*)
module L = Logging
module F = Format
(* ============== START of ADT node and proc_desc ============== *)
(* =============== START of module Node =============== *)
module Node = struct
type nodekind =
| Start_node of proc_desc
| Exit_node of proc_desc
| Stmt_node of string
| Join_node
| Prune_node of bool * Sil.if_kind * string (** (true/false branch, if_kind, comment) *)
| Skip_node of string
and t = { (** a node *)
nd_id : int; (** unique id of the node *)
mutable nd_dist_exit : int option; (** distance to the exit node *)
mutable nd_temps : Ident.t list; (** temporary variables *)
mutable nd_dead_pvars_after : Sil.pvar list; (** dead program variables after executing the instructions *)
mutable nd_dead_pvars_before : Sil.pvar list; (** dead program variables before executing the instructions *)
mutable nd_exn : t list; (** exception nodes in the cfg *)
mutable nd_instrs : Sil.instr list; (** instructions for symbolic execution *)
mutable nd_kind : nodekind; (** kind of node *)
mutable nd_loc : Location.t; (** location in the source code *)
mutable nd_preds : t list; (** predecessor nodes in the cfg *)
mutable nd_proc : proc_desc option; (** proc desc from cil *)
mutable nd_succs : t list; (** successor nodes in the cfg *)
}
and proc_desc = { (** procedure description *)
pd_attributes : ProcAttributes.t; (** attributes of the procedure *)
pd_id : int; (** unique proc_desc identifier *)
mutable pd_nodes : t list; (** list of nodes of this procedure *)
mutable pd_start_node : t; (** start node of this procedure *)
mutable pd_exit_node : t; (** exit node of ths procedure *)
}
let exn_handler_kind = Stmt_node "exception handler"
let exn_sink_kind = Stmt_node "exceptions sink"
let throw_kind = Stmt_node "throw"
type cfg = (** data type for the control flow graph *)
{ node_id : int ref;
node_list : t list ref;
name_pdesc_tbl : proc_desc Procname.Hash.t; (** Map proc name to procdesc *)
mutable priority_set : Procname.Set.t (** set of function names to be analyzed first *) }
let create_cfg () = (** create a new empty cfg *)
{ node_id = ref 0;
node_list = ref [];
name_pdesc_tbl = Procname.Hash.create 1000;
priority_set = Procname.Set.empty }
(** 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 pd2 =
(* map of exp names in pd1 -> exp names in pd2 *)
let exp_map = ref Sil.ExpMap.empty in
(* map of node id's in pd1 -> node id's in pd2 *)
let id_map = ref IntMap.empty in
(* formals are the same if their types are the same *)
let formals_eq formals1 formals2 =
IList.equal (fun (_, typ1) (_, typ2) -> Sil.typ_compare typ1 typ2) formals1 formals2 in
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 n2 =
let id_compare n1 n2 =
let id1, id2 = n1.nd_id, n2.nd_id in
try
let id1_mapping = IntMap.find id1 !id_map in
Pervasives.compare id1_mapping id2
with
Not_found ->
(* assume id's are equal and enforce by adding to [id_map] *)
id_map := IntMap.add id1 id2 !id_map;
0 in
let instrs_eq instrs1 instrs2 =
IList.equal
(fun i1 i2 ->
let n, exp_map' = Sil.instr_compare_structural i1 i2 !exp_map in
exp_map := exp_map';
n)
instrs1
instrs2 in
id_compare n1 n2 = 0 &&
IList.equal id_compare n1.nd_succs n2.nd_succs &&
IList.equal id_compare n1.nd_preds n2.nd_preds &&
instrs_eq n1.nd_instrs n2.nd_instrs in
try
IList.for_all2 node_eq n1s n2s
with Invalid_argument _ -> false in
let att1 = pd1.pd_attributes and att2 = pd2.pd_attributes in
att1.ProcAttributes.is_defined = att2.ProcAttributes.is_defined &&
Sil.typ_equal att1.ProcAttributes.ret_type att2.ProcAttributes.ret_type &&
formals_eq att1.ProcAttributes.formals att2.ProcAttributes.formals &&
nodes_eq pd1.pd_nodes pd2.pd_nodes in
let old_procs = cfg_old.name_pdesc_tbl in
let new_procs = cfg_new.name_pdesc_tbl in
let mark_pdesc_if_unchanged pname new_pdesc =
try
let old_pdesc = Procname.Hash.find old_procs pname in
let changed = not (pdescs_eq old_pdesc new_pdesc) in
new_pdesc.pd_attributes.changed <- changed
with Not_found -> () in
Procname.Hash.iter mark_pdesc_if_unchanged new_procs
let node_id_gen cfg = incr cfg.node_id; !(cfg.node_id)
let pdesc_tbl_add cfg proc_name proc_desc =
Procname.Hash.add cfg.name_pdesc_tbl proc_name proc_desc
let pdesc_tbl_remove cfg proc_name =
Procname.Hash.remove cfg.name_pdesc_tbl proc_name
let pdesc_tbl_find cfg proc_name =
Procname.Hash.find cfg.name_pdesc_tbl proc_name
let iter_proc_desc cfg f =
Procname.Hash.iter f cfg.name_pdesc_tbl
let dummy () = {
nd_id = 0;
nd_dist_exit = None;
nd_temps = [];
nd_dead_pvars_after = [];
nd_dead_pvars_before = [];
nd_instrs = [];
nd_kind = Skip_node "dummy";
nd_loc = Location.dummy;
nd_proc = None;
nd_succs = []; nd_preds = []; nd_exn = [];
}
let compare node1 node2 =
int_compare node1.nd_id node2.nd_id
let hash node =
Hashtbl.hash node.nd_id
let equal node1 node2 =
(compare node1 node2 = 0)
let get_all_nodes cfg = !(cfg.node_list)
let create cfg loc kind instrs pdesc temps =
let node_id = node_id_gen cfg in
let node =
{ nd_id = node_id;
nd_dist_exit = None;
nd_temps = temps;
nd_dead_pvars_after = [];
nd_dead_pvars_before = [];
nd_instrs = instrs;
nd_kind = kind;
nd_loc = loc;
nd_preds = [];
nd_proc = Some pdesc;
nd_succs = [];
nd_exn = []
} in
cfg.node_list := node :: !(cfg.node_list);
pdesc.pd_nodes <- node :: pdesc.pd_nodes;
node
(** Get the unique id of the node *)
let get_id node = node.nd_id
let get_succs node = node.nd_succs
type node = t
module NodeSet = Set.Make(struct
type t = node
let compare = compare
end)
let get_sliced_succs node f =
let visited = ref NodeSet.empty in
let rec slice_nodes nodes : NodeSet.t =
let do_node acc n =
visited := NodeSet.add n !visited;
if f n then NodeSet.singleton n
else NodeSet.union acc (slice_nodes (IList.filter (fun s -> not (NodeSet.mem s !visited)) n.nd_succs)) in
IList.fold_left do_node NodeSet.empty nodes in
NodeSet.elements (slice_nodes node.nd_succs)
let get_sliced_preds node f =
let visited = ref NodeSet.empty in
let rec slice_nodes nodes : NodeSet.t =
let do_node acc n =
visited := NodeSet.add n !visited;
if f n then NodeSet.singleton n
else NodeSet.union acc (slice_nodes (IList.filter (fun s -> not (NodeSet.mem s !visited)) n.nd_preds)) in
IList.fold_left do_node NodeSet.empty nodes in
NodeSet.elements (slice_nodes node.nd_preds)
let get_exn node = node.nd_exn
let set_proc_desc node proc = node.nd_proc <- Some proc
(** Set the successor nodes and exception nodes, and build predecessor links *)
let set_succs_exn node succs exn =
node.nd_succs <- succs;
node.nd_exn <- exn;
IList.iter (fun n -> n.nd_preds <- (node :: n.nd_preds)) succs
(** Get the predecessors of the node *)
let get_preds node = node.nd_preds
(** Generates a list of nodes starting at a given node and recursively adding the results of the generator *)
let get_generated_slope start_node generator =
let visited = ref NodeSet.empty in
let rec nodes n =
visited := NodeSet.add n !visited;
let succs = IList.filter (fun n -> not (NodeSet.mem n !visited)) (generator n) in
match IList.length succs with
| 1 -> n:: (nodes (IList.hd succs))
| _ -> [n] in
nodes start_node
(** Get the node kind *)
let get_kind node = node.nd_kind
(** Set the node kind *)
let set_kind node kind = node.nd_kind <- kind
(** Comparison for node kind *)
let kind_compare k1 k2 = match k1, k2 with
| Start_node pd1, Start_node pd2 ->
int_compare pd1.pd_id pd2.pd_id
| Start_node _, _ -> -1
| _, Start_node _ -> 1
| Exit_node pd1, Exit_node pd2 ->
int_compare pd1.pd_id pd2.pd_id
| Exit_node _, _ -> -1
| _, Exit_node _ -> 1
| Stmt_node s1, Stmt_node s2 ->
string_compare s1 s2
| Stmt_node _, _ -> -1
| _, Stmt_node _ -> 1
| Join_node, Join_node -> 0
| Join_node, _ -> -1
| _, Join_node -> 1
| Prune_node (is_true_branch1, if_kind1, descr1),
Prune_node (is_true_branch2, if_kind2, descr2) ->
let n = bool_compare is_true_branch1 is_true_branch2 in
if n <> 0 then n else let n = Pervasives.compare if_kind1 if_kind2 in
if n <> 0 then n else string_compare descr1 descr2
| Prune_node _, _ -> -1
| _, Prune_node _ -> 1
| Skip_node s1, Skip_node s2 ->
string_compare s1 s2
(** Get the instructions to be executed *)
let get_instrs node =
node.nd_instrs
(** Get the list of callee procnames from the node *)
let get_callees node =
let collect callees instr =
match instr with
| Sil.Call (_, exp, _, _, _) ->
begin
match exp with
| Sil.Const (Sil.Cfun procname) -> procname:: callees
| _ -> callees
end
| _ -> callees in
IList.fold_left collect [] (get_instrs node)
(** Get the location of the node *)
let get_loc n = n.nd_loc
(** Get the source location of the last instruction in the node *)
let get_last_loc n =
match IList.rev (get_instrs n) with
| instr :: _ -> Sil.instr_get_loc instr
| [] -> n.nd_loc
(** Set the location of the node *)
let set_loc n loc = n.nd_loc <- loc
let pp f node =
F.fprintf f "%n" (get_id node)
(** Get the proc desc of the node *)
let get_proc_desc node =
let proc_desc = match node.nd_proc with
| None ->
L.out "node_get_proc_desc: at node %d@\n" node.nd_id;
assert false
| Some proc_desc -> proc_desc in
proc_desc
let proc_desc_from_name cfg proc_name =
try Some (pdesc_tbl_find cfg proc_name)
with Not_found -> None
let set_temps node temps =
node.nd_temps <- temps
let get_temps node =
node.nd_temps
let set_dead_pvars node after dead =
if after then node.nd_dead_pvars_after <- dead
else node.nd_dead_pvars_before <- dead
let get_dead_pvars node after =
if after then node.nd_dead_pvars_after
else node.nd_dead_pvars_before
let get_distance_to_exit node =
node.nd_dist_exit
(** Append the instructions and temporaries to the list of instructions to execute *)
let append_instrs_temps node instrs temps =
node.nd_instrs <- node.nd_instrs @ instrs;
node.nd_temps <- node.nd_temps @ temps
(** Add the instructions and temporaties at the beginning of the list of instructions to execute *)
let prepend_instrs_temps node instrs temps =
node.nd_instrs <- instrs @ node.nd_instrs;
node.nd_temps <- temps @ node.nd_temps
(** Replace the instructions to be executed. *)
let replace_instrs node instrs =
node.nd_instrs <- instrs
let proc_desc_get_ret_var pdesc =
Sil.get_ret_pvar pdesc.pd_attributes.ProcAttributes.proc_name
(** Add declarations for local variables and return variable to the node *)
let add_locals_ret_declaration node locals =
let loc = get_loc node in
let pdesc = get_proc_desc node in
let proc_name = pdesc.pd_attributes.ProcAttributes.proc_name in
let ret_var =
let ret_type = pdesc.pd_attributes.ProcAttributes.ret_type in
(proc_desc_get_ret_var pdesc, ret_type) in
let construct_decl (x, typ) =
(Sil.mk_pvar x proc_name, typ) in
let ptl = ret_var :: IList.map construct_decl locals in
let instr = Sil.Declare_locals (ptl, loc) in
prepend_instrs_temps node [instr] []
(** Counter for identifiers of procdescs *)
let proc_desc_id_counter = ref 0
let proc_desc_create cfg proc_attributes =
incr proc_desc_id_counter;
let pdesc =
{
pd_attributes = proc_attributes;
pd_id = !proc_desc_id_counter;
pd_nodes = [];
pd_start_node = dummy ();
pd_exit_node = dummy ();
} in
pdesc_tbl_add cfg proc_attributes.ProcAttributes.proc_name pdesc;
pdesc
let remove_node' filter_out_fun cfg =
let remove_node_in_cfg nodes =
IList.filter filter_out_fun nodes in
cfg.node_list := remove_node_in_cfg !(cfg.node_list)
let remove_node_set cfg nodes =
remove_node' (fun node' -> not (NodeSet.mem node' nodes)) cfg
let proc_desc_remove cfg name remove_nodes =
(if remove_nodes then
let pdesc = pdesc_tbl_find cfg name in
let proc_nodes =
IList.fold_right (fun node set -> NodeSet.add node set)
pdesc.pd_nodes NodeSet.empty in
remove_node_set cfg proc_nodes);
pdesc_tbl_remove cfg name
let proc_desc_get_start_node proc_desc =
proc_desc.pd_start_node
let proc_desc_get_err_log proc_desc =
proc_desc.pd_attributes.ProcAttributes.err_log
let proc_desc_get_attributes proc_desc =
proc_desc.pd_attributes
let proc_desc_get_exit_node proc_desc =
proc_desc.pd_exit_node
(** Compute the distance of each node to the exit node, if not computed already *)
let proc_desc_compute_distance_to_exit_node proc_desc =
let exit_node = proc_desc.pd_exit_node in
let rec mark_distance dist nodes =
let next_nodes = ref [] in
let do_node node =
match node.nd_dist_exit with
| Some _ -> ()
| None ->
node.nd_dist_exit <- Some dist;
next_nodes := node.nd_preds @ !next_nodes in
IList.iter do_node nodes;
if !next_nodes != [] then mark_distance (dist + 1) !next_nodes in
mark_distance 0 [exit_node]
(** Set the start node of the proc desc *)
let proc_desc_set_start_node pdesc node =
pdesc.pd_start_node <- node
(** Set the exit node of the proc desc *)
let proc_desc_set_exit_node pdesc node =
pdesc.pd_exit_node <- node
(** Set a flag for the proc desc *)
let proc_desc_set_flag pdesc key value =
proc_flags_add pdesc.pd_attributes.ProcAttributes.proc_flags key value
(** Return the return type of the procedure *)
let proc_desc_get_ret_type proc_desc =
proc_desc.pd_attributes.ProcAttributes.ret_type
let proc_desc_get_proc_name proc_desc =
proc_desc.pd_attributes.ProcAttributes.proc_name
(** Return [true] iff the procedure is defined, and not just declared *)
let proc_desc_is_defined proc_desc =
proc_desc.pd_attributes.ProcAttributes.is_defined
let proc_desc_get_loc proc_desc =
proc_desc.pd_attributes.ProcAttributes.loc
(** Return name and type of formal parameters *)
let proc_desc_get_formals proc_desc =
proc_desc.pd_attributes.ProcAttributes.formals
(** Return name and type of local variables *)
let proc_desc_get_locals proc_desc =
proc_desc.pd_attributes.ProcAttributes.locals
(** Return name and type of captured variables *)
let proc_desc_get_captured proc_desc =
proc_desc.pd_attributes.ProcAttributes.captured
(** Return the visibility attribute *)
let proc_desc_get_access proc_desc =
proc_desc.pd_attributes.ProcAttributes.access
let proc_desc_get_nodes proc_desc =
proc_desc.pd_nodes
(** List of nodes in the procedure up to the first branching *)
let proc_desc_get_slope proc_desc =
get_generated_slope (proc_desc_get_start_node proc_desc) get_succs
(** List of nodes in the procedure sliced by a predicate up to the first branching *)
let proc_desc_get_sliced_slope proc_desc f =
get_generated_slope (proc_desc_get_start_node proc_desc) (fun n -> get_sliced_succs n f)
(** Get flags for the proc desc *)
let proc_desc_get_flags proc_desc =
proc_desc.pd_attributes.ProcAttributes.proc_flags
(** Append the locals to the list of local variables *)
let proc_desc_append_locals proc_desc new_locals =
proc_desc.pd_attributes.ProcAttributes.locals <-
proc_desc.pd_attributes.ProcAttributes.locals @ new_locals
(** Print extended instructions for the node, highlighting the given subinstruction if present *)
let pp_instr pe0 ~sub_instrs instro fmt node =
let pe = match instro with
| None -> pe0
| Some instr -> pe_extend_colormap pe0 (Obj.repr instr) Red in
let instrs = get_instrs node in
let pp_loc fmt () = F.fprintf fmt " %a " Location.pp (get_loc node) in
let print_sub_instrs () = F.fprintf fmt "%a" (Sil.pp_instr_list pe) instrs in
match get_kind node with
| Stmt_node s ->
if sub_instrs then print_sub_instrs ()
else F.fprintf fmt "statements (%s) %a" s pp_loc ()
| Prune_node (_, _, descr) ->
if sub_instrs then print_sub_instrs ()
else F.fprintf fmt "assume %s %a" descr pp_loc ()
| Exit_node _ ->
if sub_instrs then print_sub_instrs ()
else F.fprintf fmt "exit %a" pp_loc ()
| Skip_node s ->
if sub_instrs then print_sub_instrs ()
else F.fprintf fmt "skip (%s) %a" s pp_loc ()
| Start_node _ ->
if sub_instrs then print_sub_instrs ()
else F.fprintf fmt "start %a" pp_loc ()
| Join_node ->
if sub_instrs then print_sub_instrs ()
else F.fprintf fmt "join %a" pp_loc ()
(** Dump extended instructions for the node *)
let d_instrs ~(sub_instrs: bool) (curr_instr: Sil.instr option) (node: t) =
L.add_print_action (L.PTnode_instrs, Obj.repr (sub_instrs, curr_instr, node))
(** Return a description of the cfg node *)
let get_description pe node =
let str =
match get_kind node with
| Stmt_node _ ->
"Instructions"
| Prune_node (_, _, descr) ->
"Conditional" ^ " " ^ descr
| Exit_node _ ->
"Exit"
| Skip_node _ ->
"Skip"
| Start_node _ ->
"Start"
| Join_node ->
"Join" in
let pp fmt () = F.fprintf fmt "%s\n%a@?" str (pp_instr pe None ~sub_instrs: true) node in
pp_to_string pp ()
let proc_desc_iter_nodes f proc_desc =
IList.iter f (IList.rev (proc_desc_get_nodes proc_desc))
let proc_desc_fold_nodes f acc proc_desc =
IList.fold_left f acc (IList.rev (proc_desc_get_nodes proc_desc))
let proc_desc_fold_calls f acc pdesc =
let do_node a node =
IList.fold_left
(fun b callee_pname -> f b (callee_pname, get_loc node))
a (get_callees node) in
IList.fold_left do_node acc (proc_desc_get_nodes pdesc)
(** iterate over the calls from the procedure: (callee,location) pairs *)
let proc_desc_iter_calls f pdesc =
proc_desc_fold_calls (fun _ call -> f call) () pdesc
let proc_desc_iter_slope f proc_desc =
let visited = ref NodeSet.empty in
let rec do_node node = begin
visited := NodeSet.add node !visited;
f node;
match get_succs node with
| [n] -> if not (NodeSet.mem n !visited) then do_node n
| _ -> ()
end in
do_node (proc_desc_get_start_node proc_desc)
(** iterate between two nodes or until we reach a branching structure *)
let proc_desc_iter_slope_range f src_node dst_node =
let visited = ref NodeSet.empty in
let rec do_node node = begin
visited := NodeSet.add node !visited;
f node;
match get_succs node with
| [n] ->
if not (NodeSet.mem n !visited)
&& not (equal node dst_node)
then do_node n
| _ -> ()
end in
do_node src_node
let proc_desc_iter_slope_calls f proc_desc =
let do_node node =
IList.iter
(fun callee_pname -> f callee_pname)
(get_callees node) in
proc_desc_iter_slope do_node proc_desc
let proc_desc_iter_instrs f proc_desc =
let do_node node =
IList.iter (fun i -> f node i) (get_instrs node) in
proc_desc_iter_nodes do_node proc_desc
let proc_desc_fold_instrs f acc proc_desc =
let fold_node acc node =
IList.fold_left (fun acc instr -> f acc node instr) acc (get_instrs node) in
proc_desc_fold_nodes fold_node acc proc_desc
(*
(** Set the proc desc of the node *)
let node_set_proc_desc pdesc node =
node.nd_proc <- Some pdesc
let remove_node cfg node =
remove_node' (fun node' -> not (equal node node'))
cfg node
*)
end
(* =============== END of module Node =============== *)
type node = Node.t
type cfg = Node.cfg
(* =============== START of module Procdesc =============== *)
module Procdesc = struct
type t = Node.proc_desc
let compute_distance_to_exit_node = Node.proc_desc_compute_distance_to_exit_node
let create = Node.proc_desc_create
let remove = Node.proc_desc_remove
let find_from_name = Node.proc_desc_from_name
let get_attributes = Node.proc_desc_get_attributes
let get_err_log = Node.proc_desc_get_err_log
let get_exit_node = Node.proc_desc_get_exit_node
let get_flags = Node.proc_desc_get_flags
let get_formals = Node.proc_desc_get_formals
let get_loc = Node.proc_desc_get_loc
let get_locals = Node.proc_desc_get_locals
let get_captured = Node.proc_desc_get_captured
let get_access = Node.proc_desc_get_access
let get_nodes = Node.proc_desc_get_nodes
let get_slope = Node.proc_desc_get_slope
let get_sliced_slope = Node.proc_desc_get_sliced_slope
let get_proc_name = Node.proc_desc_get_proc_name
let get_ret_type = Node.proc_desc_get_ret_type
let get_ret_var pdesc = Sil.mk_pvar Ident.name_return (get_proc_name pdesc)
let get_start_node = Node.proc_desc_get_start_node
let is_defined = Node.proc_desc_is_defined
let iter_nodes = Node.proc_desc_iter_nodes
let fold_calls = Node.proc_desc_fold_calls
let iter_calls = Node.proc_desc_iter_calls
let iter_instrs = Node.proc_desc_iter_instrs
let fold_instrs = Node.proc_desc_fold_instrs
let iter_slope = Node.proc_desc_iter_slope
let iter_slope_calls = Node.proc_desc_iter_slope_calls
let iter_slope_range = Node.proc_desc_iter_slope_range
let set_exit_node = Node.proc_desc_set_exit_node
let set_flag = Node.proc_desc_set_flag
let set_start_node = Node.proc_desc_set_start_node
let append_locals = Node.proc_desc_append_locals
end
(* =============== END of module Procdesc =============== *)
(** Hash table with nodes as keys. *)
module NodeHash = Hashtbl.Make(Node)
(** Set of nodes. *)
module NodeSet = Node.NodeSet
let iter_proc_desc = Node.iter_proc_desc
let rec pp_node_list f = function
| [] -> ()
| [node] -> Node.pp f node
| node:: nodes ->
F.fprintf f "%a, %a" Node.pp node pp_node_list nodes
(** Get all the procdescs (defined and declared) *)
let get_all_procs cfg =
let procs = ref [] in
let f _ pdesc = procs := pdesc :: !procs in
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)
(** get the function names which should be analyzed before the other ones *)
let get_priority_procnames cfg =
cfg.Node.priority_set
(** set the function names whose address has been taken in this file *)
let set_procname_priority cfg pname =
cfg.Node.priority_set <- Procname.Set.add pname cfg.Node.priority_set
(** add instructions to remove temporaries *)
let add_removetemps_instructions cfg =
let all_nodes = Node.get_all_nodes cfg in
let do_node node =
let loc = Node.get_last_loc node in
let temps = Node.get_temps node in
if temps != [] then Node.append_instrs_temps node [Sil.Remove_temps (temps, loc)] [] in
IList.iter do_node all_nodes
(** add instructions to perform abstraction *)
let add_abstraction_instructions cfg =
let converging_node node = (* true if there is a succ node s.t.: it is an exit node, or the succ of >1 nodes *)
let is_exit node = match Node.get_kind node with
| Node.Exit_node _ -> true
| _ -> false in
let succ_nodes = Node.get_succs node in
if IList.exists is_exit succ_nodes then true
else match succ_nodes with
| [] -> false
| [h] -> IList.length (Node.get_preds h) > 1
| _ -> false in
let node_requires_abstraction node =
match Node.get_kind node with
| Node.Start_node _
| Node.Join_node ->
false
| Node.Exit_node _
| Node.Stmt_node _
| Node.Prune_node _
| Node.Skip_node _ ->
converging_node node in
let all_nodes = Node.get_all_nodes cfg in
let do_node node =
let loc = Node.get_last_loc node in
if node_requires_abstraction node then Node.append_instrs_temps node [Sil.Abstract loc] [] in
IList.iter do_node all_nodes
let get_name_of_local (curr_f : Procdesc.t) (x, _) =
Sil.mk_pvar x (Procdesc.get_proc_name curr_f)
(* returns a list of local static variables (ie local variables defined static) in a proposition *)
let get_name_of_objc_static_locals (curr_f : Procdesc.t) p =
let pname = Procname.to_string (Procdesc.get_proc_name curr_f) in
let local_static e =
match e with (* is a local static if it's a global and it has a static local name *)
| Sil.Lvar pvar when (Sil.pvar_is_global pvar) && (Sil.is_static_local_name pname pvar) -> [pvar]
| _ -> [] in
let hpred_local_static hpred =
match hpred with
| Sil.Hpointsto(e, _, _) -> [local_static e]
| _ -> [] in
let vars_sigma = IList.map hpred_local_static (Prop.get_sigma p) in
IList.flatten (IList.flatten vars_sigma)
(* returns a list of local variables that points to an objc block in a proposition *)
let get_name_of_objc_block_locals p =
let local_blocks e =
match e with
| Sil.Lvar pvar when (Sil.is_block_pvar pvar) ->
[pvar]
| _ -> [] in
let hpred_local_blocks hpred =
match hpred with
| Sil.Hpointsto(e, _, _) -> [local_blocks e]
| _ -> [] in
let vars_sigma = IList.map hpred_local_blocks (Prop.get_sigma p) in
IList.flatten (IList.flatten vars_sigma)
let remove_abducted_retvars p =
(* compute the hpreds and pure atoms reachable from the set of seed expressions in [exps] *)
let compute_reachable p seed_exps =
let sigma, pi = Prop.get_sigma p, Prop.get_pi p in
let rec collect_exps exps = function
| Sil.Eexp (Sil.Const (Sil.Cexn e), _) -> Sil.ExpSet.add e exps
| Sil.Eexp (e, _) -> Sil.ExpSet.add e exps
| Sil.Estruct (flds, _) ->
IList.fold_left (fun exps (_, strexp) -> collect_exps exps strexp) exps flds
| Sil.Earray (_, elems, _) ->
IList.fold_left (fun exps (_, strexp) -> collect_exps exps strexp) exps elems in
let rec compute_reachable_hpreds_rec sigma (reach, exps) =
let add_hpred_if_reachable (reach, exps) = function
| Sil.Hpointsto (lhs, rhs, _) as hpred when Sil.ExpSet.mem lhs exps ->
let reach' = Sil.HpredSet.add hpred reach in
let exps' = collect_exps exps rhs in
(reach', exps')
| _ -> reach, exps in
let reach', exps' = IList.fold_left add_hpred_if_reachable (reach, exps) sigma in
if (Sil.HpredSet.cardinal reach) = (Sil.HpredSet.cardinal reach') then (reach, exps)
else compute_reachable_hpreds_rec sigma (reach', exps') in
let reach_hpreds, reach_exps =
compute_reachable_hpreds_rec sigma (Sil.HpredSet.empty, seed_exps) in
(* filter away the pure atoms without reachable exps *)
let reach_pi =
let rec exp_contains = function
| exp when Sil.ExpSet.mem exp reach_exps -> true
| Sil.UnOp (_, e, _) | Sil.Cast (_, e) | Sil.Lfield (e, _, _) -> exp_contains e
| Sil.BinOp (_, e0, e1) | Sil.Lindex (e0, e1) -> exp_contains e0 || exp_contains e1
| _ -> false in
IList.filter
(function
| Sil.Aeq (lhs, rhs) | Sil.Aneq (lhs, rhs) -> exp_contains lhs || exp_contains rhs)
pi in
Sil.HpredSet.elements reach_hpreds, reach_pi in
(* separate the abducted pvars from the normal ones, deallocate the abducted ones*)
let abducted_pvars, normal_pvars =
IList.fold_left
(fun pvars hpred ->
match hpred with
| Sil.Hpointsto (Sil.Lvar pvar, _, _) ->
let abducted_pvars, normal_pvars = pvars in
if Sil.pvar_is_abducted pvar then pvar :: abducted_pvars, normal_pvars
else abducted_pvars, pvar :: normal_pvars
| _ -> pvars)
([], [])
(Prop.get_sigma p) in
let _, p' = Prop.deallocate_stack_vars p abducted_pvars in
let normal_pvar_set =
IList.fold_left
(fun normal_pvar_set pvar -> Sil.ExpSet.add (Sil.Lvar pvar) normal_pvar_set)
Sil.ExpSet.empty
normal_pvars in
(* walk forward from non-abducted pvars, keep everything reachable. remove everything else *)
let sigma_reach, pi_reach = compute_reachable p' normal_pvar_set in
Prop.normalize (Prop.replace_pi pi_reach (Prop.replace_sigma sigma_reach p'))
let remove_locals (curr_f : Procdesc.t) p =
let names_of_locals = IList.map (get_name_of_local curr_f) (Procdesc.get_locals curr_f) in
let names_of_locals' = match !Config.curr_language with
| Config.C_CPP -> (* in ObjC to deal with block we need to remove static locals *)
let names_of_static_locals = get_name_of_objc_static_locals curr_f p in
let names_of_block_locals = get_name_of_objc_block_locals p in
names_of_block_locals @ names_of_locals @ names_of_static_locals
| _ -> names_of_locals in
let removed, p' = Prop.deallocate_stack_vars p names_of_locals' in
(removed, if !Config.angelic_execution then remove_abducted_retvars p' else p')
let remove_formals (curr_f : Procdesc.t) p =
let pname = Procdesc.get_proc_name curr_f in
let formal_vars = IList.map (fun (n, _) -> Sil.mk_pvar n pname) (Procdesc.get_formals curr_f) in
Prop.deallocate_stack_vars p formal_vars
(** remove the return variable from the prop *)
let remove_ret (curr_f : Procdesc.t) (p: Prop.normal Prop.t) =
let pname = Procdesc.get_proc_name curr_f in
let name_of_ret = Procdesc.get_ret_var curr_f in
let _, p' = Prop.deallocate_stack_vars p [(Sil.pvar_to_callee pname name_of_ret)] in
p'
(** remove locals and return variable from the prop *)
let remove_locals_ret (curr_f : Procdesc.t) p =
snd (remove_locals curr_f (remove_ret curr_f p))
(** Remove locals and formal parameters from the prop.
Return the list of stack variables whose address was still present after deallocation. *)
let remove_locals_formals (curr_f : Procdesc.t) p =
let pvars1, p1 = remove_formals curr_f p in
let pvars2, p2 = remove_locals curr_f p1 in
pvars1 @ pvars2, p2
(** remove seed vars from a prop *)
let remove_seed_vars (prop: 'a Prop.t) : Prop.normal Prop.t =
let hpred_not_seed = function
| Sil.Hpointsto(Sil.Lvar pv, _, _) -> not (Sil.pvar_is_seed pv)
| _ -> true in
let sigma = Prop.get_sigma prop in
let sigma' = IList.filter hpred_not_seed sigma in
Prop.normalize (Prop.replace_sigma sigma' prop)
(** checks whether a cfg is connected or not *)
let check_cfg_connectedness cfg =
let is_exit_node n =
match Node.get_kind n with
| Node.Exit_node _ -> true
| _ -> false in
let broken_node n =
let succs = Node.get_succs n in
let preds = Node.get_preds n in
match Node.get_kind n with
| Node.Start_node _ -> (IList.length succs = 0) || (IList.length preds > 0)
| Node.Exit_node _ -> (IList.length succs > 0) || (IList.length preds = 0)
| Node.Stmt_node _ | Node.Prune_node _
| Node.Skip_node _ -> (IList.length succs = 0) || (IList.length preds = 0)
| 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 *)
(match succs with
| [n'] when is_exit_node n' -> false
| _ -> (IList.length preds = 0)) in
let do_pdesc pd =
let pname = Procname.to_string (Procdesc.get_proc_name pd) in
let nodes = Procdesc.get_nodes pd in
let broken = IList.exists broken_node nodes in
if broken then
L.out "\n ***BROKEN CFG: '%s'\n" pname
else
L.out "\n ***CONNECTED CFG: '%s'\n" pname in
let pdescs = get_all_procs cfg in
IList.iter do_pdesc pdescs
(** Given a mangled name of a block return its procdesc if exists*)
let get_block_pdesc cfg block =
let pdescs = get_defined_procs cfg in
let is_block_pdesc pd =
let name = Procdesc.get_proc_name pd in
(Procname.to_string name) = (Mangled.to_string block) in
try
let block_pdesc = IList.find is_block_pdesc pdescs in
Some block_pdesc
with Not_found -> None
(** Removes seeds variables from a prop corresponding to captured variables in an objc block *)
let remove_seed_captured_vars_block captured_vars prop =
let is_captured pname vn = Mangled.equal pname vn in
let hpred_seed_captured = function
| Sil.Hpointsto(Sil.Lvar pv, _, _) ->
let pname = Sil.pvar_get_name pv in
(Sil.pvar_is_seed pv) && (IList.mem is_captured pname captured_vars)
| _ -> false in
let sigma = Prop.get_sigma prop in
let sigma' = IList.filter (fun hpred -> not (hpred_seed_captured hpred)) sigma in
Prop.normalize (Prop.replace_sigma sigma' prop)
(** Serializer for control flow graphs *)
let cfg_serializer : cfg Serialization.serializer =
Serialization.create_serializer Serialization.cfg_key
(** Load a cfg from a file *)
let load_cfg_from_file (filename : DB.filename) : cfg option =
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 = Node.proc_desc_get_loc pdesc in
let source_file = loc.Location.file in
let source_file_str = DB.source_file_to_abs_path source_file in
let dest_file = DB.source_file_in_resdir source_file in
let dest_file_str = DB.filename_to_string dest_file in
let needs_copy =
Node.proc_desc_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) in
if needs_copy then
match copy_file source_file_str dest_file_str with
| Some _ -> ()
| None -> L.err "Error cannot create copy of source file %s@." source_file_str in
Node.iter_proc_desc cfg process_proc
(** Save the .attr files for the procedures in the cfg. *)
let save_attributes cfg =
let save_proc proc_desc =
let attributes = Procdesc.get_attributes proc_desc in
let loc = attributes.ProcAttributes.loc in
let attributes' =
if Location.equal loc Location.dummy then
let loc' = {loc with Location.file = !DB.current_source } in
{attributes with ProcAttributes.loc = loc'}
else
attributes in
(*
L.stderr "save_proc@. proc_name:%a@. filename:%s@. current_source:%s@. loc:%s@."
Procname.pp (Procdesc.get_proc_name proc_desc)
(DB.filename_to_string filename)
(DB.source_file_to_string !DB.current_source)
(Location.to_string loc);
*)
AttributesTable.store_attributes attributes' in
IList.iter save_proc (get_all_procs cfg)
(** Inline a synthetic (access or bridge) method. *)
let inline_synthetic_method ret_ids etl proc_desc loc_call : Sil.instr option =
let modified = ref None in
let debug = false in
let found instr instr' =
modified := Some instr';
if debug then
begin
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'
end in
let do_instr _ instr =
match instr, ret_ids, etl with
| Sil.Letderef (_, Sil.Lfield (Sil.Var _, fn, ft), bt, _),
[ret_id],
[(e1, _)] -> (* getter for fields *)
let instr' = Sil.Letderef (ret_id, Sil.Lfield (e1, fn, ft), bt, loc_call) in
found instr instr'
| Sil.Letderef (_, Sil.Lfield (Sil.Lvar pvar, fn, ft), bt, _), [ret_id], []
when Sil.pvar_is_global pvar -> (* getter for static fields *)
let instr' = Sil.Letderef (ret_id, Sil.Lfield (Sil.Lvar pvar, fn, ft), bt, loc_call) in
found instr instr'
| Sil.Set (Sil.Lfield (_, fn, ft), bt , _, _),
_,
[(e1, _); (e2, _)] -> (* setter for fields *)
let instr' = Sil.Set (Sil.Lfield (e1, fn, ft), bt , e2, loc_call) in
found instr instr'
| Sil.Set (Sil.Lfield (Sil.Lvar pvar, fn, ft), bt , _, _), _, [(e1, _)]
when Sil.pvar_is_global pvar -> (* setter for static fields *)
let instr' = Sil.Set (Sil.Lfield (Sil.Lvar pvar, fn, ft), bt , e1, loc_call) in
found instr instr'
| Sil.Call (ret_ids', Sil.Const (Sil.Cfun pn), etl', _, cf), _, _
when IList.length ret_ids = IList.length ret_ids'
&& IList.length etl' = IList.length etl ->
let instr' = Sil.Call (ret_ids, Sil.Const (Sil.Cfun pn), etl, loc_call, cf) in
found instr instr'
| Sil.Call (ret_ids', Sil.Const (Sil.Cfun pn), etl', _, cf), _, _
when IList.length ret_ids = IList.length ret_ids'
&& IList.length etl' + 1 = IList.length etl ->
let etl1 = match IList.rev etl with (* remove last element *)
| _ :: l -> IList.rev l
| [] -> assert false in
let instr' = Sil.Call (ret_ids, Sil.Const (Sil.Cfun pn), etl1, loc_call, cf) in
found instr instr'
| _ -> () in
Procdesc.iter_instrs do_instr proc_desc;
!modified
(** Find synthetic (access or bridge) Java methods in the procedure and inline them in the cfg. *)
let proc_inline_synthetic_methods cfg proc_desc : unit =
let instr_inline_synthetic_method = function
| Sil.Call (ret_ids, Sil.Const (Sil.Cfun pn), etl, loc, _) ->
(match Procdesc.find_from_name cfg pn with
| Some pd ->
let is_access = Procname.java_is_access_method pn in
let attributes = Procdesc.get_attributes pd in
let is_synthetic = attributes.ProcAttributes.is_synthetic_method in
let is_bridge = attributes.ProcAttributes.is_bridge_method in
if is_access || is_bridge || is_synthetic
then inline_synthetic_method ret_ids etl pd loc
else None
| None -> None)
| _ -> None in
let node_inline_synthetic_methods node =
let modified = ref false in
let do_instr instr = match instr_inline_synthetic_method instr with
| None -> instr
| Some instr' ->
modified := true;
instr' in
let instrs = Node.get_instrs node in
let instrs' = IList.map do_instr instrs in
if !modified then Node.replace_instrs node instrs' in
Procdesc.iter_nodes node_inline_synthetic_methods proc_desc
(** Inline the java synthetic methods in the cfg *)
let inline_java_synthetic_methods cfg =
let f proc_name proc_desc =
if Procname.is_java proc_name
then proc_inline_synthetic_methods cfg proc_desc in
iter_proc_desc cfg f
(** Save a cfg into a file *)
let store_cfg_to_file (filename : DB.filename) (save_sources : bool) (cfg : cfg) =
inline_java_synthetic_methods cfg;
if save_sources then save_source_files cfg;
if !Config.incremental_procs then
begin
match load_cfg_from_file filename with
| Some old_cfg -> Node.mark_unchanged_pdescs cfg old_cfg
| None -> ()
end;
save_attributes cfg;
Serialization.to_file cfg_serializer filename cfg