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.

439 lines
12 KiB

/*
* vim: set ft=rust:
* vim: set ft=reason:
*
* Copyright (c) 2009 - 2013 Monoidics ltd.
* Copyright (c) 2013 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
open! Utils;
/** Module for call graphs */
let module L = Logging;
let module F = Format;
type node = 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 = {
/** defined procedure as opposed to just declared */
mutable defined: bool,
mutable parents: Procname.Set.t,
mutable children: Procname.Set.t,
/** ancestors are computed lazily */
mutable ancestors: option Procname.Set.t,
/** heirs are computed lazily */
mutable heirs: option Procname.Set.t,
/** recursive dependents are computed lazily */
mutable recursive_dependents: option Procname.Set.t,
/** calls are computed lazily */
mutable in_out_calls: option in_out_calls
};
/** Type for call graph */
type t = {
mutable source: DB.source_file, /** path for the source file */
mutable nLOC: int, /** number of LOC */
node_map: Procname.Hash.t node_info /** map from node to node_info */
};
let create () => {source: !DB.current_source, nLOC: !Config.nLOC, node_map: Procname.Hash.create 3};
let add_node g n defined::defined =>
try {
let info = Procname.Hash.find g.node_map n;
/* defined and disabled only go from false to true
to avoid accidental overwrite to false by calling add_edge */
if defined {
info.defined = true
}
} {
| Not_found =>
let info = {
defined,
parents: Procname.Set.empty,
children: Procname.Set.empty,
ancestors: None,
heirs: None,
recursive_dependents: None,
in_out_calls: None
};
Procname.Hash.add g.node_map n info
};
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 (Procname.Set.singleton node);
let seen = ref Procname.Set.empty;
let result = ref Procname.Set.empty;
while (not (Procname.Set.is_empty !todo)) {
let current = Procname.Set.choose !todo;
todo := Procname.Set.remove current !todo;
if (not (Procname.Set.mem current !seen)) {
seen := Procname.Set.add current !seen;
let info = Procname.Hash.find g current;
switch info.ancestors {
| Some ancestors => result := Procname.Set.union !result ancestors
| None =>
result := Procname.Set.union !result info.parents;
todo := Procname.Set.union !todo info.parents
}
}
};
!result
};
/** Compute the heirs of the node, if not already computed */
let compute_heirs g node => {
let todo = ref (Procname.Set.singleton node);
let seen = ref Procname.Set.empty;
let result = ref Procname.Set.empty;
while (not (Procname.Set.is_empty !todo)) {
let current = Procname.Set.choose !todo;
todo := Procname.Set.remove current !todo;
if (not (Procname.Set.mem current !seen)) {
seen := Procname.Set.add current !seen;
let info = Procname.Hash.find g current;
switch info.heirs {
| Some heirs => result := Procname.Set.union !result heirs
| None =>
result := Procname.Set.union !result info.children;
todo := Procname.Set.union !todo info.children
}
}
};
!result
};
/** Compute the ancestors of the node, if not pre-computed already */
let get_ancestors (g: t) node => {
let info = Procname.Hash.find g.node_map node;
switch info.ancestors {
| None =>
let ancestors = compute_ancestors g.node_map node;
info.ancestors = Some ancestors;
let size = Procname.Set.cardinal ancestors;
if (size > 1000) {
L.err "%a has %d ancestors@." 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 = Procname.Hash.find g.node_map node;
switch info.heirs {
| None =>
let heirs = compute_heirs g.node_map node;
info.heirs = Some heirs;
let size = Procname.Set.cardinal heirs;
if (size > 1000) {
L.err "%a has %d heirs@." Procname.pp node size
};
heirs
| Some heirs => heirs
}
};
let node_defined (g: t) n =>
try {
let info = Procname.Hash.find g.node_map n;
info.defined
} {
| Not_found => false
};
let add_edge g nfrom nto => {
add_node g nfrom defined::false;
add_node g nto defined::false;
let info_from = Procname.Hash.find g.node_map nfrom;
let info_to = Procname.Hash.find g.node_map nto;
info_from.children = Procname.Set.add nto info_from.children;
info_to.parents = 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 [];
Procname.Hash.iter (fun node info => table := [(node, info), ...!table]) g.node_map;
let cmp (n1: Procname.t, _) (n2: Procname.t, _) => Procname.compare n1 n2;
IList.iter (fun (n, info) => f n info) (IList.sort cmp !table)
};
let get_nodes (g: t) => {
let nodes = ref Procname.Set.empty;
let f node _ => nodes := Procname.Set.add node !nodes;
node_map_iter f g;
!nodes
};
let compute_calls g node => {
in_calls: Procname.Set.cardinal (get_ancestors g node),
out_calls: 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 = Procname.Hash.find g.node_map node;
switch info.in_out_calls {
| None =>
let calls = compute_calls g node;
info.in_out_calls = Some calls;
calls
| Some calls => calls
}
};
let get_all_nodes (g: t) => {
let nodes = Procname.Set.elements (get_nodes g);
IList.map (fun node => (node, get_calls g node)) nodes
};
let get_nodes_and_calls (g: t) => IList.filter (fun (n, _) => node_defined g n) (get_all_nodes g);
let node_get_num_ancestors g n => (n, Procname.Set.cardinal (get_ancestors g n));
let get_edges (g: t) :list ((node, int), (node, int)) => {
let edges = ref [];
let f node info =>
Procname.Set.iter
(
fun nto => edges := [
(node_get_num_ancestors g node, node_get_num_ancestors g nto),
...!edges
]
)
info.children;
node_map_iter f g;
!edges
};
/** Return all the children of [n], whether defined or not */
let get_all_children (g: t) n => (Procname.Hash.find g.node_map n).children;
/** Return the children of [n] which are defined */
let get_defined_children (g: t) n => Procname.Set.filter (node_defined g) (get_all_children g n);
/** Return the parents of [n] */
let get_parents (g: t) n => (Procname.Hash.find g.node_map n).parents;
/** Check if [source] recursively calls [dest] */
let calls_recursively (g: t) source dest => 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 (Procname.Set.mem pn (get_ancestors g n));
let res0 = Procname.Set.filter is_not_recursive (get_all_children g n);
let res = Procname.Set.filter (node_defined g) res0;
res
};
/** Return the ancestors of [n] which are also heirs of [n] */
let compute_recursive_dependents (g: t) n => {
let reached_from_n pn => Procname.Set.mem n (get_ancestors g pn);
let res0 = Procname.Set.filter reached_from_n (get_ancestors g n);
let res = Procname.Set.filter (node_defined g) res0;
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 = Procname.Hash.find g.node_map n;
switch info.recursive_dependents {
| None =>
let recursive_dependents = compute_recursive_dependents g n;
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 =>
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 Procname.Set.empty;
node_map_iter
(
fun n info =>
if info.defined {
nodes := Procname.Set.add n !nodes
}
)
g;
let nodes_list = Procname.Set.elements !nodes;
IList.map (fun n => (n, get_defined_children g n)) nodes_list
};
/** nodes with defined flag, and edges */
type nodes_and_edges = (list (node, bool), list (node, node));
/** 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 [];
let edges = ref [];
let do_children node nto => edges := [(node, nto), ...!edges];
let f node info => {
nodes := [(node, info.defined), ...!nodes];
Procname.Set.iter (do_children node) info.children
};
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;
let get_node (node, _) => node;
IList.map get_node (IList.filter (fun (_, defined) => defined) nodes)
};
/** Return the path of the source file */
let get_source (g: t) => g.source;
/** Return the number of LOC of the source file */
let get_nLOC (g: t) => g.nLOC;
/** [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;
IList.iter (fun (node, defined) => add_node cg_old node defined::defined) nodes;
IList.iter (fun (nfrom, nto) => add_edge cg_old nfrom nto) edges
};
/** Begin support for serialization */
let callgraph_serializer: Serialization.serializer (DB.source_file, int, nodes_and_edges) = Serialization.create_serializer Serialization.cg_key;
/** Load a call graph from a file */
let load_from_file (filename: DB.filename) :option t => {
let g = create ();
switch (Serialization.from_file callgraph_serializer filename) {
| None => None
| Some (source, nLOC, (nodes, edges)) =>
IList.iter
(
fun (node, defined) =>
if defined {
add_defined_node g node
}
)
nodes;
IList.iter (fun (nfrom, nto) => add_edge g nfrom nto) edges;
g.source = source;
g.nLOC = nLOC;
Some g
}
};
/** Save a call graph into a file */
let store_to_file (filename: DB.filename) (call_graph: t) =>
Serialization.to_file
callgraph_serializer
filename
(call_graph.source, call_graph.nLOC, get_nodes_and_edges call_graph);
let pp_graph_dotty get_specs (g: t) fmt => {
let nodes_with_calls = get_all_nodes g;
let num_specs n =>
try (IList.length (get_specs n)) {
| exn when SymOp.exn_not_failure exn => (-1)
};
let get_color (n, _) =>
if (num_specs n !== 0) {
"green"
} else {
"red"
};
let get_shape (n, _) =>
if (node_defined g n) {
"box"
} else {
"diamond"
};
let pp_node fmt (n, _) => F.fprintf fmt "\"%s\"" (Procname.to_filename n);
let pp_node_label fmt (n, calls) =>
F.fprintf
fmt
"\"%a | calls=%d %d | specs=%d)\""
Procname.pp
n
calls.in_calls
calls.out_calls
(num_specs n);
F.fprintf fmt "digraph {@\n";
IList.iter
(
fun nc =>
F.fprintf
fmt
"%a [shape=box,label=%a,color=%s,shape=%s]@\n"
pp_node
nc
pp_node_label
nc
(get_color nc)
(get_shape nc)
)
nodes_with_calls;
IList.iter (fun (s, d) => F.fprintf fmt "%a -> %a@\n" pp_node s pp_node d) (get_edges g);
F.fprintf fmt "}@."
};
/** Print the current call graph as a dotty file.
If the filename is [None], use the current file dir inside the DB dir. */
let save_call_graph_dotty fname_opt get_specs (g: t) => {
let fname_dot =
switch fname_opt {
| None => DB.Results_dir.path_to_filename DB.Results_dir.Abs_source_dir ["call_graph.dot"]
| Some fname => fname
};
let outc = open_out (DB.filename_to_string fname_dot);
let fmt = F.formatter_of_out_channel outc;
pp_graph_dotty get_specs g fmt;
close_out outc
};