/* * 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! IStd; let module Hashtbl = Caml.Hashtbl; let module L = Logging; let module F = Format; /* =============== START of module Node =============== */ let module Node = { type id = int [@@deriving compare]; let equal_id = [%compare.equal : id]; type nodekind = | Start_node Procname.t | Exit_node Procname.t | Stmt_node string | Join_node | Prune_node bool Sil.if_kind string /** (true/false branch, if_kind, comment) */ | Skip_node string [@@deriving compare]; let equal_nodekind = [%compare.equal : nodekind]; /** a node */ type t = { /** unique id of the node */ id: id, /** distance to the exit node */ mutable dist_exit: option int, /** exception nodes in the cfg */ mutable exn: list t, /** instructions for symbolic execution */ mutable instrs: list Sil.instr, /** kind of node */ kind: nodekind, /** location in the source code */ loc: Location.t, /** predecessor nodes in the cfg */ mutable preds: list t, /** name of the procedure the node belongs to */ pname_opt: option Procname.t, /** successor nodes in the cfg */ mutable succs: list t }; let exn_handler_kind = Stmt_node "exception handler"; let exn_sink_kind = Stmt_node "exceptions sink"; let throw_kind = Stmt_node "throw"; let dummy pname_opt => { id: 0, dist_exit: None, instrs: [], kind: Skip_node "dummy", loc: Location.dummy, pname_opt, succs: [], preds: [], exn: [] }; let compare node1 node2 => Int.compare node1.id node2.id; let hash node => Hashtbl.hash node.id; let equal = [%compare.equal : t]; /** Get the unique id of the node */ let get_id node => node.id; let get_succs node => node.succs; type node = t; let module NodeSet = Caml.Set.Make { type t = node; let compare = compare; }; let module IdMap = Caml.Map.Make { type t = id; let compare = compare_id; }; let get_sliced_succs node f => { let visited = ref NodeSet.empty; let rec slice_nodes nodes :NodeSet.t => { let do_node acc n => { visited := NodeSet.add n !visited; if (f n) { NodeSet.singleton n } else { NodeSet.union acc (slice_nodes (List.filter f::(fun s => not (NodeSet.mem s !visited)) n.succs)) } }; List.fold f::do_node init::NodeSet.empty nodes }; NodeSet.elements (slice_nodes node.succs) }; let get_sliced_preds node f => { let visited = ref NodeSet.empty; let rec slice_nodes nodes :NodeSet.t => { let do_node acc n => { visited := NodeSet.add n !visited; if (f n) { NodeSet.singleton n } else { NodeSet.union acc (slice_nodes (List.filter f::(fun s => not (NodeSet.mem s !visited)) n.preds)) } }; List.fold f::do_node init::NodeSet.empty nodes }; NodeSet.elements (slice_nodes node.preds) }; let get_exn node => node.exn; /** Get the name of the procedure the node belongs to */ let get_proc_name node => switch node.pname_opt { | None => L.out "get_proc_name: at node %d@\n" node.id; assert false | Some pname => pname }; /** Get the predecessors of the node */ let get_preds node => node.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; let rec nodes n => { visited := NodeSet.add n !visited; let succs = List.filter f::(fun n => not (NodeSet.mem n !visited)) (generator n); switch succs { | [hd] => [n, ...nodes hd] | _ => [n] } }; nodes start_node }; /** Get the node kind */ let get_kind node => node.kind; /** Get the instructions to be executed */ let get_instrs node => node.instrs; /** Get the list of callee procnames from the node */ let get_callees node => { let collect callees instr => switch instr { | Sil.Call _ exp _ _ _ => switch exp { | Exp.Const (Const.Cfun procname) => [procname, ...callees] | _ => callees } | _ => callees }; List.fold f::collect init::[] (get_instrs node) }; /** Get the location of the node */ let get_loc n => n.loc; /** Get the source location of the last instruction in the node */ let get_last_loc n => switch (IList.rev (get_instrs n)) { | [instr, ..._] => Sil.instr_get_loc instr | [] => n.loc }; let pp_id f id => F.fprintf f "%d" id; let pp f node => pp_id f (get_id node); let get_distance_to_exit node => node.dist_exit; /** Append the instructions to the list of instructions to execute */ let append_instrs node instrs => node.instrs = node.instrs @ instrs; /** Add the instructions at the beginning of the list of instructions to execute */ let prepend_instrs node instrs => node.instrs = instrs @ node.instrs; /** Replace the instructions to be executed. */ let replace_instrs node instrs => node.instrs = instrs; /** Add declarations for local variables and return variable to the node */ let add_locals_ret_declaration node (proc_attributes: ProcAttributes.t) locals => { let loc = get_loc node; let pname = proc_attributes.proc_name; let ret_var = { let ret_type = proc_attributes.ret_type; (Pvar.get_ret_pvar pname, ret_type) }; let construct_decl (x, typ) => (Pvar.mk x pname, typ); let ptl = [ret_var, ...IList.map construct_decl locals]; let instr = Sil.Declare_locals ptl loc; prepend_instrs node [instr] }; /** Print extended instructions for the node, highlighting the given subinstruction if present */ let pp_instrs pe0 sub_instrs::sub_instrs instro fmt node => { let pe = switch instro { | None => pe0 | Some instr => Pp.extend_colormap pe0 (Obj.repr instr) Red }; let instrs = get_instrs node; let pp_loc fmt () => F.fprintf fmt " %a " Location.pp (get_loc node); let print_sub_instrs () => F.fprintf fmt "%a" (Sil.pp_instr_list pe) instrs; switch (get_kind node) { | Stmt_node s => if sub_instrs { print_sub_instrs () } else { F.fprintf fmt "statements (%s) %a" s pp_loc () } | Prune_node _ _ descr => if sub_instrs { print_sub_instrs () } else { F.fprintf fmt "assume %s %a" descr pp_loc () } | Exit_node _ => if sub_instrs { print_sub_instrs () } else { F.fprintf fmt "exit %a" pp_loc () } | Skip_node s => if sub_instrs { print_sub_instrs () } else { F.fprintf fmt "skip (%s) %a" s pp_loc () } | Start_node _ => if sub_instrs { print_sub_instrs () } else { F.fprintf fmt "start %a" pp_loc () } | Join_node => if sub_instrs { print_sub_instrs () } else { F.fprintf fmt "join %a" pp_loc () } } }; /** Dump extended instructions for the node */ let d_instrs sub_instrs::(sub_instrs: bool) (curr_instr: option Sil.instr) (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 = switch (get_kind node) { | Stmt_node _ => "Instructions" | Prune_node _ _ descr => "Conditional" ^ " " ^ descr | Exit_node _ => "Exit" | Skip_node _ => "Skip" | Start_node _ => "Start" | Join_node => "Join" }; let pp fmt => F.fprintf fmt "%s\n%a@?" str (pp_instrs pe None sub_instrs::true) node; F.asprintf "%t" pp }; }; /* =============== END of module Node =============== */ /** Map over nodes */ let module NodeMap = Caml.Map.Make Node; /** Hash table with nodes as keys. */ let module NodeHash = Hashtbl.Make Node; /** Set of nodes. */ let module NodeSet = Node.NodeSet; /** Map with node id keys. */ let module IdMap = Node.IdMap; /** procedure description */ type t = { attributes: ProcAttributes.t, /** attributes of the procedure */ mutable nodes: list Node.t, /** list of nodes of this procedure */ mutable nodes_num: int, /** number of nodes */ mutable start_node: Node.t, /** start node of this procedure */ mutable exit_node: Node.t, /** exit node of ths procedure */ mutable loop_heads: option NodeSet.t /** loop head nodes of this procedure */ } [@@deriving compare]; /** Only call from Cfg */ let from_proc_attributes called_from_cfg::called_from_cfg attributes => { if (not called_from_cfg) { assert false }; let pname_opt = Some attributes.ProcAttributes.proc_name; let start_node = Node.dummy pname_opt; let exit_node = Node.dummy pname_opt; {attributes, nodes: [], nodes_num: 0, start_node, exit_node, loop_heads: None} }; /** Compute the distance of each node to the exit node, if not computed already */ let compute_distance_to_exit_node pdesc => { let exit_node = pdesc.exit_node; let rec mark_distance dist nodes => { let next_nodes = ref []; let do_node (node: Node.t) => switch node.dist_exit { | Some _ => () | None => node.dist_exit = Some dist; next_nodes := node.preds @ !next_nodes }; List.iter f::do_node nodes; if (!next_nodes != []) { mark_distance (dist + 1) !next_nodes } }; mark_distance 0 [exit_node] }; /** check or indicate if we have performed preanalysis on the CFG */ let did_preanalysis pdesc => pdesc.attributes.did_preanalysis; let signal_did_preanalysis pdesc => pdesc.attributes.did_preanalysis = true; let get_attributes pdesc => pdesc.attributes; let get_err_log pdesc => pdesc.attributes.err_log; let get_exit_node pdesc => pdesc.exit_node; /** Get flags for the proc desc */ let get_flags pdesc => pdesc.attributes.proc_flags; /** Return name and type of formal parameters */ let get_formals pdesc => pdesc.attributes.formals; let get_loc pdesc => pdesc.attributes.loc; /** Return name and type of local variables */ let get_locals pdesc => pdesc.attributes.locals; /** Return name and type of captured variables */ let get_captured pdesc => pdesc.attributes.captured; /** Return the visibility attribute */ let get_access pdesc => pdesc.attributes.access; let get_nodes pdesc => pdesc.nodes; let get_proc_name pdesc => pdesc.attributes.proc_name; /** Return the return type of the procedure */ let get_ret_type pdesc => pdesc.attributes.ret_type; let get_ret_var pdesc => Pvar.mk Ident.name_return (get_proc_name pdesc); let get_start_node pdesc => pdesc.start_node; /** List of nodes in the procedure sliced by a predicate up to the first branching */ let get_sliced_slope pdesc f => Node.get_generated_slope (get_start_node pdesc) (fun n => Node.get_sliced_succs n f); /** List of nodes in the procedure up to the first branching */ let get_slope pdesc => Node.get_generated_slope (get_start_node pdesc) Node.get_succs; /** Return [true] iff the procedure is defined, and not just declared */ let is_defined pdesc => pdesc.attributes.is_defined; let is_body_empty pdesc => List.is_empty (Node.get_succs (get_start_node pdesc)); let is_java_synchronized pdesc => pdesc.attributes.is_java_synchronized_method; let iter_nodes f pdesc => List.iter f::f (IList.rev (get_nodes pdesc)); let fold_calls f acc pdesc => { let do_node a node => List.fold f::(fun b callee_pname => f b (callee_pname, Node.get_loc node)) init::a (Node.get_callees node); List.fold f::do_node init::acc (get_nodes pdesc) }; /** iterate over the calls from the procedure: (callee,location) pairs */ let iter_calls f pdesc => fold_calls (fun _ call => f call) () pdesc; let iter_instrs f pdesc => { let do_node node => List.iter f::(fun i => f node i) (Node.get_instrs node); iter_nodes do_node pdesc }; let fold_nodes f acc pdesc => List.fold f::f init::acc (IList.rev (get_nodes pdesc)); let fold_instrs f acc pdesc => { let fold_node acc node => List.fold f::(fun acc instr => f acc node instr) init::acc (Node.get_instrs node); fold_nodes fold_node acc pdesc }; let iter_slope f pdesc => { let visited = ref NodeSet.empty; let rec do_node node => { visited := NodeSet.add node !visited; f node; switch (Node.get_succs node) { | [n] => if (not (NodeSet.mem n !visited)) { do_node n } | _ => () } }; do_node (get_start_node pdesc) }; let iter_slope_calls f pdesc => { let do_node node => List.iter f::(fun callee_pname => f callee_pname) (Node.get_callees node); iter_slope do_node pdesc }; /** iterate between two nodes or until we reach a branching structure */ let iter_slope_range f src_node dst_node => { let visited = ref NodeSet.empty; let rec do_node node => { visited := NodeSet.add node !visited; f node; switch (Node.get_succs node) { | [n] => if (not (NodeSet.mem n !visited) && not (Node.equal node dst_node)) { do_node n } | _ => () } }; do_node src_node }; /** Set the exit node of the proc desc */ let set_exit_node pdesc node => pdesc.exit_node = node; /** Set a flag for the proc desc */ let set_flag pdesc key value => ProcAttributes.proc_flags_add pdesc.attributes.proc_flags key value; /** Set the start node of the proc desc */ let set_start_node pdesc node => pdesc.start_node = node; /** Append the locals to the list of local variables */ let append_locals pdesc new_locals => pdesc.attributes.locals = pdesc.attributes.locals @ new_locals; /** Set the successor nodes and exception nodes, and build predecessor links */ let set_succs_exn_base (node: Node.t) succs exn => { node.succs = succs; node.exn = exn; List.iter f::(fun (n: Node.t) => n.preds = [node, ...n.preds]) succs }; /** Create a new cfg node */ let create_node pdesc loc kind instrs => { pdesc.nodes_num = pdesc.nodes_num + 1; let node_id = pdesc.nodes_num; let node = { Node.id: node_id, dist_exit: None, instrs, kind, loc, preds: [], pname_opt: Some pdesc.attributes.proc_name, succs: [], exn: [] }; pdesc.nodes = [node, ...pdesc.nodes]; node }; /** Set the successor and exception nodes. If this is a join node right before the exit node, add an extra node in the middle, otherwise nullify and abstract instructions cannot be added after a conditional. */ let node_set_succs_exn pdesc (node: Node.t) succs exn => switch (node.kind, succs) { | (Join_node, [{Node.kind: Exit_node _} as exit_node]) => let kind = Node.Stmt_node "between_join_and_exit"; let node' = create_node pdesc node.loc kind node.instrs; set_succs_exn_base node [node'] exn; set_succs_exn_base node' [exit_node] exn | _ => set_succs_exn_base node succs exn }; /** Get loop heads for widening. It collects all target nodes of back-edges in a depth-first traversal. */ let get_loop_heads pdesc => { let rec set_loop_head_rec visited heads wl => switch wl { | [] => heads | [(n, ancester), ...wl'] => if (NodeSet.mem n visited) { if (NodeSet.mem n ancester) { set_loop_head_rec visited (NodeSet.add n heads) wl' } else { set_loop_head_rec visited heads wl' } } else { let ancester = NodeSet.add n ancester; let succs = List.append (Node.get_succs n) (Node.get_exn n); let works = IList.map (fun m => (m, ancester)) succs; set_loop_head_rec (NodeSet.add n visited) heads (List.append works wl') } }; let start_wl = [(get_start_node pdesc, NodeSet.empty)]; let lh = set_loop_head_rec NodeSet.empty NodeSet.empty start_wl; pdesc.loop_heads = Some lh; lh }; let is_loop_head pdesc (node: Node.t) => { let lh = switch pdesc.loop_heads { | Some lh => lh | None => get_loop_heads pdesc }; NodeSet.mem node lh }; let pp_variable_list fmt etl => if (List.is_empty etl) { Format.fprintf fmt "None" } else { IList.iter (fun (id, ty) => Format.fprintf fmt " %a:%a" Mangled.pp id (Typ.pp_full Pp.text) ty) etl }; let pp_signature fmt pdesc => { let pname = get_proc_name pdesc; let pname_string = Procname.to_string pname; let defined_string = is_defined pdesc ? "defined" : "undefined"; Format.fprintf fmt "%s [%s, Return type: %s, Formals: %a, Locals: %a" pname_string defined_string (Typ.to_string (get_ret_type pdesc)) pp_variable_list (get_formals pdesc) pp_variable_list (get_locals pdesc); if (not (List.is_empty (get_captured pdesc))) { Format.fprintf fmt ", Captured: %a" pp_variable_list (get_captured pdesc) }; let attributes = get_attributes pdesc; let method_annotation = attributes.ProcAttributes.method_annotation; if (not (Annot.Method.is_empty method_annotation)) { Format.fprintf fmt ", Annotation: %a" (Annot.Method.pp pname_string) method_annotation }; Format.fprintf fmt "]@\n" };