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.
314 lines
12 KiB
314 lines
12 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 for call graphs *)
|
|
|
|
open! IStd
|
|
module Hashtbl = Caml.Hashtbl
|
|
module L = Logging
|
|
module F = Format
|
|
|
|
type node = Typ.Procname.t
|
|
|
|
type in_out_calls =
|
|
{ in_calls: int (** total number of in calls transitively *)
|
|
; out_calls: int (** total number of out calls transitively *) }
|
|
|
|
type node_info =
|
|
{ mutable defined: bool (** defined procedure as opposed to just declared *)
|
|
; mutable parents: Typ.Procname.Set.t
|
|
; mutable children: Typ.Procname.Set.t
|
|
; mutable ancestors: Typ.Procname.Set.t option (** ancestors are computed lazily *)
|
|
; mutable heirs: Typ.Procname.Set.t option (** heirs are computed lazily *)
|
|
; mutable recursive_dependents: Typ.Procname.Set.t option
|
|
(** recursive dependents are computed lazily *)
|
|
; mutable in_out_calls: in_out_calls option (** calls are computed lazily *) }
|
|
|
|
(** Type for call graph *)
|
|
type t =
|
|
{ source: SourceFile.t (** path for the source file *)
|
|
; node_map: node_info Typ.Procname.Hash.t (** map from node to node_info *) }
|
|
|
|
let create source = {source; node_map= Typ.Procname.Hash.create 3}
|
|
|
|
let add_node g n ~defined =
|
|
try
|
|
let info = Typ.Procname.Hash.find g.node_map n in
|
|
(* defined and disabled only go from false to true
|
|
to avoid accidental overwrite to false by calling add_edge *)
|
|
if defined then info.defined <- true
|
|
with Not_found ->
|
|
let info =
|
|
{ defined
|
|
; parents= Typ.Procname.Set.empty
|
|
; children= Typ.Procname.Set.empty
|
|
; ancestors= None
|
|
; heirs= None
|
|
; recursive_dependents= None
|
|
; in_out_calls= None }
|
|
in
|
|
Typ.Procname.Hash.add g.node_map n info
|
|
|
|
let remove_node_defined g n =
|
|
try
|
|
let info = Typ.Procname.Hash.find g.node_map n in
|
|
info.defined <- false
|
|
with Not_found -> ()
|
|
|
|
let add_defined_node g n = add_node g n ~defined:true
|
|
|
|
(** Compute the ancestors of the node, if not already computed *)
|
|
let compute_ancestors g node =
|
|
let todo = ref (Typ.Procname.Set.singleton node) in
|
|
let seen = ref Typ.Procname.Set.empty in
|
|
let result = ref Typ.Procname.Set.empty in
|
|
while not (Typ.Procname.Set.is_empty !todo) do
|
|
let current = Typ.Procname.Set.choose !todo in
|
|
todo := Typ.Procname.Set.remove current !todo ;
|
|
if not (Typ.Procname.Set.mem current !seen) then (
|
|
seen := Typ.Procname.Set.add current !seen ;
|
|
let info = Typ.Procname.Hash.find g current in
|
|
match info.ancestors with
|
|
| Some ancestors
|
|
-> result := Typ.Procname.Set.union !result ancestors
|
|
| None
|
|
-> result := Typ.Procname.Set.union !result info.parents ;
|
|
todo := Typ.Procname.Set.union !todo info.parents )
|
|
done ;
|
|
!result
|
|
|
|
(** Compute the heirs of the node, if not already computed *)
|
|
let compute_heirs g node =
|
|
let todo = ref (Typ.Procname.Set.singleton node) in
|
|
let seen = ref Typ.Procname.Set.empty in
|
|
let result = ref Typ.Procname.Set.empty in
|
|
while not (Typ.Procname.Set.is_empty !todo) do
|
|
let current = Typ.Procname.Set.choose !todo in
|
|
todo := Typ.Procname.Set.remove current !todo ;
|
|
if not (Typ.Procname.Set.mem current !seen) then (
|
|
seen := Typ.Procname.Set.add current !seen ;
|
|
let info = Typ.Procname.Hash.find g current in
|
|
match info.heirs with
|
|
| Some heirs
|
|
-> result := Typ.Procname.Set.union !result heirs
|
|
| None
|
|
-> result := Typ.Procname.Set.union !result info.children ;
|
|
todo := Typ.Procname.Set.union !todo info.children )
|
|
done ;
|
|
!result
|
|
|
|
(** Compute the ancestors of the node, if not pre-computed already *)
|
|
let get_ancestors (g: t) node =
|
|
let info = Typ.Procname.Hash.find g.node_map node in
|
|
match info.ancestors with
|
|
| None
|
|
-> let ancestors = compute_ancestors g.node_map node in
|
|
info.ancestors <- Some ancestors ;
|
|
let size = Typ.Procname.Set.cardinal ancestors in
|
|
if size > 1000 then
|
|
L.(debug Analysis Medium) "%a has %d ancestors@." Typ.Procname.pp node size ;
|
|
ancestors
|
|
| Some ancestors
|
|
-> ancestors
|
|
|
|
(** Compute the heirs of the node, if not pre-computed already *)
|
|
let get_heirs (g: t) node =
|
|
let info = Typ.Procname.Hash.find g.node_map node in
|
|
match info.heirs with
|
|
| None
|
|
-> let heirs = compute_heirs g.node_map node in
|
|
info.heirs <- Some heirs ;
|
|
let size = Typ.Procname.Set.cardinal heirs in
|
|
if size > 1000 then L.(debug Analysis Medium) "%a has %d heirs@." Typ.Procname.pp node size ;
|
|
heirs
|
|
| Some heirs
|
|
-> heirs
|
|
|
|
let node_defined (g: t) n =
|
|
try
|
|
let info = Typ.Procname.Hash.find g.node_map n in
|
|
info.defined
|
|
with Not_found -> false
|
|
|
|
let add_edge g nfrom nto =
|
|
add_node g nfrom ~defined:false ;
|
|
add_node g nto ~defined:false ;
|
|
let info_from = Typ.Procname.Hash.find g.node_map nfrom in
|
|
let info_to = Typ.Procname.Hash.find g.node_map nto in
|
|
info_from.children <- Typ.Procname.Set.add nto info_from.children ;
|
|
info_to.parents <- Typ.Procname.Set.add nfrom info_to.parents
|
|
|
|
(** iterate over the elements of a node_map in node order *)
|
|
let node_map_iter f g =
|
|
let table = ref [] in
|
|
Typ.Procname.Hash.iter (fun node info -> table := (node, info) :: !table) g.node_map ;
|
|
let cmp ((n1: Typ.Procname.t), _) ((n2: Typ.Procname.t), _) = Typ.Procname.compare n1 n2 in
|
|
List.iter ~f:(fun (n, info) -> f n info) (List.sort ~cmp !table)
|
|
|
|
let get_nodes (g: t) =
|
|
let nodes = ref Typ.Procname.Set.empty in
|
|
let f node _ = nodes := Typ.Procname.Set.add node !nodes in
|
|
node_map_iter f g ; !nodes
|
|
|
|
let compute_calls g node =
|
|
{ in_calls= Typ.Procname.Set.cardinal (get_ancestors g node)
|
|
; out_calls= Typ.Procname.Set.cardinal (get_heirs g node) }
|
|
|
|
(** Compute the calls of the node, if not pre-computed already *)
|
|
let get_calls (g: t) node =
|
|
let info = Typ.Procname.Hash.find g.node_map node in
|
|
match info.in_out_calls with
|
|
| None
|
|
-> let calls = compute_calls g node in
|
|
info.in_out_calls <- Some calls ;
|
|
calls
|
|
| Some calls
|
|
-> calls
|
|
|
|
let get_all_nodes (g: t) =
|
|
let nodes = Typ.Procname.Set.elements (get_nodes g) in
|
|
List.map ~f:(fun node -> (node, get_calls g node)) nodes
|
|
|
|
let get_nodes_and_calls (g: t) = List.filter ~f:(fun (n, _) -> node_defined g n) (get_all_nodes g)
|
|
|
|
let node_get_num_ancestors g n = (n, Typ.Procname.Set.cardinal (get_ancestors g n))
|
|
|
|
let get_edges (g: t) : ((node * int) * (node * int)) list =
|
|
let edges = ref [] in
|
|
let f node info =
|
|
Typ.Procname.Set.iter
|
|
(fun nto -> edges := (node_get_num_ancestors g node, node_get_num_ancestors g nto) :: !edges)
|
|
info.children
|
|
in
|
|
node_map_iter f g ; !edges
|
|
|
|
(** Return all the children of [n], whether defined or not *)
|
|
let get_all_children (g: t) n = (Typ.Procname.Hash.find g.node_map n).children
|
|
|
|
(** Return the children of [n] which are defined *)
|
|
let get_defined_children (g: t) n = Typ.Procname.Set.filter (node_defined g) (get_all_children g n)
|
|
|
|
(** Return the parents of [n] *)
|
|
let get_parents (g: t) n = (Typ.Procname.Hash.find g.node_map n).parents
|
|
|
|
(** Check if [source] recursively calls [dest] *)
|
|
let calls_recursively (g: t) source dest = Typ.Procname.Set.mem source (get_ancestors g dest)
|
|
|
|
(** Return the children of [n] which are not heirs of [n] *)
|
|
let get_nonrecursive_dependents (g: t) n =
|
|
let is_not_recursive pn = not (Typ.Procname.Set.mem pn (get_ancestors g n)) in
|
|
let res0 = Typ.Procname.Set.filter is_not_recursive (get_all_children g n) in
|
|
let res = Typ.Procname.Set.filter (node_defined g) res0 in
|
|
res
|
|
|
|
(** Return the ancestors of [n] which are also heirs of [n] *)
|
|
let compute_recursive_dependents (g: t) n =
|
|
let reached_from_n pn = Typ.Procname.Set.mem n (get_ancestors g pn) in
|
|
let res0 = Typ.Procname.Set.filter reached_from_n (get_ancestors g n) in
|
|
let res = Typ.Procname.Set.filter (node_defined g) res0 in
|
|
res
|
|
|
|
(** Compute the ancestors of [n] which are also heirs of [n], if not pre-computed already *)
|
|
let get_recursive_dependents (g: t) n =
|
|
let info = Typ.Procname.Hash.find g.node_map n in
|
|
match info.recursive_dependents with
|
|
| None
|
|
-> let recursive_dependents = compute_recursive_dependents g n in
|
|
info.recursive_dependents <- Some recursive_dependents ;
|
|
recursive_dependents
|
|
| Some recursive_dependents
|
|
-> recursive_dependents
|
|
|
|
(** Return the nodes dependent on [n] *)
|
|
let get_dependents (g: t) n =
|
|
Typ.Procname.Set.union (get_nonrecursive_dependents g n) (get_recursive_dependents g n)
|
|
|
|
(** Return all the nodes with their defined children *)
|
|
let get_nodes_and_defined_children (g: t) =
|
|
let nodes = ref Typ.Procname.Set.empty in
|
|
node_map_iter (fun n info -> if info.defined then nodes := Typ.Procname.Set.add n !nodes) g ;
|
|
let nodes_list = Typ.Procname.Set.elements !nodes in
|
|
List.map ~f:(fun n -> (n, get_defined_children g n)) nodes_list
|
|
|
|
(** nodes with defined flag, and edges *)
|
|
type nodes_and_edges = (node * bool) list * (node * node) list
|
|
|
|
(** Return the list of nodes, with defined+disabled flags, and the list of edges *)
|
|
let get_nodes_and_edges (g: t) : nodes_and_edges =
|
|
let nodes = ref [] in
|
|
let edges = ref [] in
|
|
let do_children node nto = edges := (node, nto) :: !edges in
|
|
let f node info =
|
|
nodes := (node, info.defined) :: !nodes ;
|
|
Typ.Procname.Set.iter (do_children node) info.children
|
|
in
|
|
node_map_iter f g ; (!nodes, !edges)
|
|
|
|
(** Return the list of nodes which are defined *)
|
|
let get_defined_nodes (g: t) =
|
|
let nodes, _ = get_nodes_and_edges g in
|
|
let get_node (node, _) = node in
|
|
List.map ~f:get_node (List.filter ~f:(fun (_, defined) -> defined) nodes)
|
|
|
|
(** Return the path of the source file *)
|
|
let get_source (g: t) = g.source
|
|
|
|
(** [extend cg1 gc2] extends [cg1] in place with nodes and edges from [gc2];
|
|
undefined nodes become defined if at least one side is. *)
|
|
let extend cg_old cg_new =
|
|
let nodes, edges = get_nodes_and_edges cg_new in
|
|
List.iter ~f:(fun (node, defined) -> add_node cg_old node ~defined) nodes ;
|
|
List.iter ~f:(fun (nfrom, nto) -> add_edge cg_old nfrom nto) edges
|
|
|
|
(** Begin support for serialization *)
|
|
let callgraph_serializer : (SourceFile.t * nodes_and_edges) Serialization.serializer =
|
|
Serialization.create_serializer Serialization.Key.cg
|
|
|
|
(** Load a call graph from a file *)
|
|
let load_from_file (filename: DB.filename) : t option =
|
|
match Serialization.read_from_file callgraph_serializer filename with
|
|
| None
|
|
-> None
|
|
| Some (source, (nodes, edges))
|
|
-> let g = create source in
|
|
List.iter ~f:(fun (node, defined) -> if defined then add_defined_node g node) nodes ;
|
|
List.iter ~f:(fun (nfrom, nto) -> add_edge g nfrom nto) edges ;
|
|
Some g
|
|
|
|
(** Save a call graph into a file *)
|
|
let store_to_file (filename: DB.filename) (call_graph: t) =
|
|
Serialization.write_to_file callgraph_serializer filename
|
|
~data:(call_graph.source, get_nodes_and_edges call_graph)
|
|
|
|
let pp_graph_dotty (g: t) fmt =
|
|
let nodes_with_calls = get_all_nodes g in
|
|
let get_shape (n, _) = if node_defined g n then "box" else "diamond" in
|
|
let pp_node fmt (n, _) = F.fprintf fmt "\"%s\"" (Typ.Procname.to_filename n) in
|
|
let pp_node_label fmt (n, calls) =
|
|
F.fprintf fmt "\"%a | calls=%d %d)\"" Typ.Procname.pp n calls.in_calls calls.out_calls
|
|
in
|
|
F.fprintf fmt "digraph {@\n" ;
|
|
List.iter
|
|
~f:(fun nc ->
|
|
F.fprintf fmt "%a [shape=box,label=%a,color=%s,shape=%s]@\n" pp_node nc pp_node_label nc
|
|
"red" (get_shape nc))
|
|
nodes_with_calls ;
|
|
List.iter ~f:(fun (s, d) -> F.fprintf fmt "%a -> %a@\n" pp_node s pp_node d) (get_edges g) ;
|
|
F.fprintf fmt "}@."
|
|
|
|
(** Print the call graph as a dotty file. *)
|
|
let save_call_graph_dotty source (g: t) =
|
|
let fname_dot =
|
|
DB.Results_dir.path_to_filename (DB.Results_dir.Abs_source_dir source) ["call_graph.dot"]
|
|
in
|
|
let outc = Out_channel.create (DB.filename_to_string fname_dot) in
|
|
let fmt = F.formatter_of_out_channel outc in
|
|
pp_graph_dotty g fmt ; Out_channel.close outc
|