(* * 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 open Utils (* No abbreviation for Utils, as every module can depend on it *) (* ============== 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 : Sil.proc_attributes; (** 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 *) pd_err_log: Errlog.t; (** error log at translation time *) mutable pd_changed : bool; (** true if proc has changed since last analysis *) } 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 = list_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 = list_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 && list_equal id_compare n1.nd_succs n2.nd_succs && list_equal id_compare n1.nd_preds n2.nd_preds && instrs_eq n1.nd_instrs n2.nd_instrs in try list_for_all2 node_eq n1s n2s with Invalid_argument _ -> false in pd1.pd_attributes.Sil.is_defined = pd2.pd_attributes.Sil.is_defined && Sil.typ_equal pd1.pd_attributes.Sil.ret_type pd2.pd_attributes.Sil.ret_type && formals_eq pd1.pd_attributes.Sil.formals pd2.pd_attributes.Sil.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_changed <- changed with Not_found -> () in Procname.Hash.iter mark_pdesc_if_unchanged new_procs let compute_enabled_verbose = false (** restrict the cfg to the given enabled (active and not shadowed) procedures *) let cfg_restrict_enabled cfg source enabled = match enabled with | None -> () | Some enabled_procs -> if compute_enabled_verbose then L.err "cfg_restrict_enabled: checking enabled in %s@." (DB.source_file_to_string source); let is_enabled pname = Procname.Set.mem pname enabled_procs in let in_address_set pname = Procname.Set.mem pname cfg.priority_set in let node_list' = let filter_node node = match node.nd_proc with | None -> true | Some pdesc -> is_enabled pdesc.pd_attributes.Sil.proc_name in list_filter filter_node !(cfg.node_list) in let procs_to_remove = let psetr = ref Procname.Set.empty in let do_proc pname pdesc = if pdesc.pd_attributes.Sil.is_defined && not (is_enabled pname) && not (in_address_set pname) then psetr := Procname.Set.add pname !psetr in Procname.Hash.iter do_proc cfg.name_pdesc_tbl; !psetr in let remove_proc pname = if compute_enabled_verbose then L.err "cfg_restrict_enabled: Removing proc not enabled from the cfg: %s@." (Procname.to_filename pname); Procname.Hash.remove cfg.name_pdesc_tbl pname in cfg.node_list := node_list'; Procname.Set.iter remove_proc procs_to_remove 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 proc_name_to_proc_desc cfg proc_name = pdesc_tbl_find cfg proc_name let proc_name_is_changed cfg proc_name = try let pdesc = proc_name_to_proc_desc cfg proc_name in pdesc.pd_changed with Not_found -> true 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.loc_none; 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 (list_filter (fun s -> not (NodeSet.mem s !visited)) n.nd_succs)) in list_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 (list_filter (fun s -> not (NodeSet.mem s !visited)) n.nd_preds)) in list_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; list_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 = list_filter (fun n -> not (NodeSet.mem n !visited)) (generator n) in match list_length succs with | 1 -> n:: (nodes (list_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 list_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 list_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 (** Set the proc desc of the node *) let node_set_proc_desc pdesc node = node.nd_proc <- Some pdesc 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.Sil.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.Sil.proc_name in let ret_var = let ret_type = pdesc.pd_attributes.Sil.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 :: list_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 remove_node' filter_out_fun cfg node = let remove_node_in_cfg nodes = list_filter filter_out_fun nodes in cfg.node_list := remove_node_in_cfg !(cfg.node_list) let remove_node cfg node = remove_node' (fun node' -> not (equal node node')) cfg node let remove_node_set cfg nodes = remove_node' (fun node' -> not (NodeSet.mem node' nodes)) cfg nodes let proc_desc_remove cfg name remove_nodes = (if remove_nodes then let pdesc = pdesc_tbl_find cfg name in let proc_nodes = list_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_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 list_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.Sil.proc_flags key value (** Return the return type of the procedure *) let proc_desc_get_ret_type proc_desc = proc_desc.pd_attributes.Sil.ret_type let proc_desc_get_proc_name proc_desc = proc_desc.pd_attributes.Sil.proc_name (** Return [true] iff the procedure is defined, and not just declared *) let proc_desc_is_defined proc_desc = proc_desc.pd_attributes.Sil.is_defined let proc_desc_get_loc proc_desc = proc_desc.pd_attributes.Sil.loc (** Return name and type of formal parameters *) let proc_desc_get_formals proc_desc = proc_desc.pd_attributes.Sil.formals (** Return name and type of local variables *) let proc_desc_get_locals proc_desc = proc_desc.pd_attributes.Sil.locals (** Return name and type of captured variables *) let proc_desc_get_captured proc_desc = proc_desc.pd_attributes.Sil.captured 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.Sil.proc_flags (** Append the locals to the list of local variables *) let proc_desc_append_locals proc_desc new_locals = proc_desc.pd_attributes.Sil.locals <- proc_desc.pd_attributes.Sil.locals @ new_locals (** Get the cyclomatic complexity for the procedure *) let proc_desc_get_cyclomatic proc_desc = let num_edges = ref 0 in let num_nodes = ref 0 in let num_connected = 1 in (* always one connected component in a procedure's cfg *) let nodes = proc_desc_get_nodes proc_desc in let do_node node = incr num_nodes; num_edges := !num_edges + list_length (get_succs node) in list_iter do_node nodes; let cyclo = !num_edges - !num_nodes + 2 * num_connected in (* formula for cyclomatic complexity *) cyclo (** 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 (is_true_branch, if_kind, 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 (is_true_branch, if_kind, descr) -> "Conditional" ^ " " ^ descr | Exit_node _ -> "Exit" | Skip_node s -> "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 = list_iter f (list_rev (proc_desc_get_nodes proc_desc)) let proc_desc_fold_nodes f acc proc_desc = (*list_fold_left (fun acc node -> f acc node) acc (list_rev (proc_desc_get_nodes proc_desc))*) list_fold_left f acc (list_rev (proc_desc_get_nodes proc_desc)) (** iterate over the calls from the procedure: (callee,location) pairs *) let proc_desc_iter_calls f pdesc = let do_node node = list_iter (fun callee_pname -> f (callee_pname, get_loc node)) (get_callees node) in list_iter do_node (proc_desc_get_nodes 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 proc_desc 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 = list_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 = list_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 = list_fold_left (fun acc instr -> f acc node instr) acc (get_instrs node) in proc_desc_fold_nodes fold_node acc proc_desc end (* =============== END of module Node =============== *) type node = Node.t type cfg = Node.cfg (** 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 pname 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 Utils.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 a cfg into a file *) let store_cfg_to_file (filename : DB.filename) (save_sources : bool) (cfg: 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; Serialization.to_file cfg_serializer filename 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 type proc_desc_builder = { cfg : cfg; proc_attributes : Sil.proc_attributes; } let create (b : proc_desc_builder) = let open Node in incr proc_desc_id_counter; let pdesc = { pd_attributes = b.proc_attributes; pd_id = !proc_desc_id_counter; pd_nodes = []; pd_start_node = dummy (); pd_exit_node = dummy (); pd_err_log = Errlog.empty (); pd_changed = true } in pdesc_tbl_add b.cfg b.proc_attributes.Sil.proc_name pdesc; pdesc 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_cyclomatic = Node.proc_desc_get_cyclomatic 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_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 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 pname 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 = list_filter Procdesc.is_defined (get_all_procs cfg) (** Get the objc procedures whose body is generated *) let get_objc_generated_procs cfg = list_filter ( fun procdesc -> (Procdesc.get_attributes procdesc).Sil.is_generated) (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 list_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 list_exists is_exit succ_nodes then true else match succ_nodes with | [] -> false | [h] -> list_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 list_iter do_node all_nodes let get_name_of_parameter (curr_f : Procdesc.t) (x, typ) = Sil.mk_pvar (Mangled.from_string x) (Procdesc.get_proc_name curr_f) let get_name_of_local (curr_f : Procdesc.t) (x, typ) = 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 = list_map hpred_local_static (Prop.get_sigma p) in list_flatten (list_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 = list_map hpred_local_blocks (Prop.get_sigma p) in list_flatten (list_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, _) -> list_fold_left (fun exps (_, strexp) -> collect_exps exps strexp) exps flds | Sil.Earray (_, elems, _) -> list_fold_left (fun exps (index, 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' = list_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 list_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 = list_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 = list_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 = list_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 names_of_formals = list_map (get_name_of_parameter curr_f) (Procdesc.get_formals curr_f) in Prop.deallocate_stack_vars p names_of_formals (** 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' = list_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 _ -> (list_length succs = 0) || (list_length preds > 0) | Node.Exit_node _ -> (list_length succs > 0) || (list_length preds = 0) | Node.Stmt_node _ | Node.Prune_node _ | Node.Skip_node _ -> (list_length succs = 0) || (list_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 | _ -> (list_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 = list_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 list_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 = list_find is_block_pdesc pdescs in Some block_pdesc with Not_found -> None (** return true if the pdesc associated with [pname] changed since the last analysis run or did not exist in the last analysis run *) let pdesc_is_changed cfg pname = Node.proc_name_is_changed cfg pname (** 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) && (list_mem is_captured pname captured_vars) | _ -> false in let sigma = Prop.get_sigma prop in let sigma' = list_filter (fun hpred -> not (hpred_seed_captured hpred)) sigma in Prop.normalize (Prop.replace_sigma sigma' prop)