checkers can now analyze a restricted subset of the procedures defined in a file

Reviewed By: jvillard

Differential Revision: D2554136

fb-gh-sync-id: c972f0e
master
Cristiano Calcagno 9 years ago committed by facebook-github-bot-7
parent a5e1743382
commit f17f54939b

@ -196,7 +196,12 @@ let iterate_cluster_callbacks all_procs exe_env proc_names =
(** Invoke all procedure and cluster callbacks on a given environment. *) (** Invoke all procedure and cluster callbacks on a given environment. *)
let iterate_callbacks store_summary call_graph exe_env = let iterate_callbacks store_summary call_graph exe_env =
let proc_names = Cg.get_defined_nodes call_graph in let procs_to_analyze =
(* analyze all the currently defined procedures *)
Cg.get_defined_nodes call_graph in
let originally_defined_procs =
(* all the defined procedures, even if we are analyzing a restricted subset *)
Cg.get_originally_defined_nodes call_graph in
let saved_language = !Config.curr_language in let saved_language = !Config.curr_language in
let cluster_id proc_name = let cluster_id proc_name =
@ -225,18 +230,18 @@ let iterate_callbacks store_summary call_graph exe_env =
(* Make sure summaries exists. *) (* Make sure summaries exists. *)
list_iter reset_summary proc_names; list_iter reset_summary procs_to_analyze;
(* Invoke callbacks. *) (* Invoke callbacks. *)
list_iter list_iter
(iterate_procedure_callbacks proc_names exe_env) (iterate_procedure_callbacks originally_defined_procs exe_env)
proc_names; procs_to_analyze;
list_iter list_iter
(iterate_cluster_callbacks proc_names exe_env) (iterate_cluster_callbacks originally_defined_procs exe_env)
(cluster proc_names); (cluster procs_to_analyze);
list_iter store_summary proc_names; list_iter store_summary procs_to_analyze;
Config.curr_language := saved_language Config.curr_language := saved_language

@ -26,7 +26,8 @@ type in_out_calls =
} }
type node_info = type node_info =
{ mutable defined : bool; { mutable defined : bool; (** defined procedure as opposed to just declared *)
mutable disabled : bool; (** originally defined procedures disabled by restrict_defined *)
mutable parents: Procname.Set.t; mutable parents: Procname.Set.t;
mutable children: Procname.Set.t; mutable children: Procname.Set.t;
mutable ancestors : Procname.Set.t option ; (** ancestors are computed lazily *) mutable ancestors : Procname.Set.t option ; (** ancestors are computed lazily *)
@ -48,13 +49,17 @@ let create () =
nLOC = !Config.nLOC; nLOC = !Config.nLOC;
node_map = Procname.Hash.create 3 } node_map = Procname.Hash.create 3 }
let _add_node g n defined = let _add_node g n defined disabled =
try try
let info = Procname.Hash.find g.node_map n in let info = Procname.Hash.find g.node_map n in
if defined then info.defined <- true (* 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;
if disabled then info.disabled <- true;
with Not_found -> with Not_found ->
let info = let info =
{ defined = defined; { defined = defined;
disabled = disabled;
parents = Procname.Set.empty; parents = Procname.Set.empty;
children = Procname.Set.empty; children = Procname.Set.empty;
ancestors = None; ancestors = None;
@ -63,8 +68,11 @@ let _add_node g n defined =
in_out_calls = None } in in_out_calls = None } in
Procname.Hash.add g.node_map n info Procname.Hash.add g.node_map n info
let add_node g n = let add_defined_node g n =
_add_node g n true _add_node g n true false
let add_disabled_node g n =
_add_node g n false true
(** Compute the ancestors of the node, if not already computed *) (** Compute the ancestors of the node, if not already computed *)
let compute_ancestors g node = let compute_ancestors g node =
@ -147,8 +155,8 @@ let node_set_defined (g: t) n defined =
with Not_found -> () with Not_found -> ()
let add_edge g nfrom nto = let add_edge g nfrom nto =
_add_node g nfrom false; _add_node g nfrom false false;
_add_node g nto false; _add_node g nto false false;
let info_from = Procname.Hash.find g.node_map nfrom in let info_from = Procname.Hash.find g.node_map nfrom in
let info_to = Procname.Hash.find g.node_map nto in let info_to = Procname.Hash.find g.node_map nto in
info_from.children <- Procname.Set.add nto info_from.children; info_from.children <- Procname.Set.add nto info_from.children;
@ -161,13 +169,18 @@ let node_map_iter f g =
let cmp ((n1: Procname.t), _) ((n2: Procname.t), _) = Procname.compare n1 n2 in let cmp ((n1: Procname.t), _) ((n2: Procname.t), _) = Procname.compare n1 n2 in
list_iter (fun (n, info) -> f n info) (list_sort cmp !table) list_iter (fun (n, info) -> f n info) (list_sort cmp !table)
(** if not None, restrict defined nodes to the given set *) (** If not None, restrict defined nodes to the given set,
and mark them as disabled. *)
let restrict_defined (g: t) (nodeset_opt: Procname.Set.t option) = let restrict_defined (g: t) (nodeset_opt: Procname.Set.t option) =
match nodeset_opt with match nodeset_opt with
| None -> () | None -> ()
| Some nodeset -> | Some nodeset ->
let f node info = let f node info =
if not (Procname.Set.mem node nodeset) then info.defined <- false in if info.defined && not (Procname.Set.mem node nodeset) then
begin
info.defined <- false;
info.disabled <- true
end in
node_map_iter f g node_map_iter f g
let get_nodes (g: t) = let get_nodes (g: t) =
@ -269,16 +282,18 @@ let get_nodes_and_defined_children (g: t) =
let nodes_list = Procname.Set.elements !nodes in let nodes_list = Procname.Set.elements !nodes in
list_map (fun n -> (n, get_defined_children g n)) nodes_list list_map (fun n -> (n, get_defined_children g n)) nodes_list
type nodes_and_edges = (node * bool) list * (node * node) list type nodes_and_edges =
(node * bool * bool) list * (* nodes with defined and disabled flag *)
(node * node) list (* edges *)
(** Return the list of nodes, with defined flag, and the list of edges *) (** 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 get_nodes_and_edges (g: t) : nodes_and_edges =
let nodes = ref [] in let nodes = ref [] in
let edges = ref [] in let edges = ref [] in
let do_children node info nto = let do_children node info nto =
edges := (node, nto) :: !edges in edges := (node, nto) :: !edges in
let f node info = let f node info =
nodes := (node, info.defined) :: !nodes; nodes := (node, info.defined, info.disabled) :: !nodes;
Procname.Set.iter (do_children node info) info.children in Procname.Set.iter (do_children node info) info.children in
node_map_iter f g; node_map_iter f g;
(!nodes, !edges) (!nodes, !edges)
@ -286,7 +301,21 @@ let get_nodes_and_edges (g: t) : nodes_and_edges =
(** Return the list of nodes which are defined *) (** Return the list of nodes which are defined *)
let get_defined_nodes (g: t) = let get_defined_nodes (g: t) =
let (nodes, _) = get_nodes_and_edges g in let (nodes, _) = get_nodes_and_edges g in
list_map fst (list_filter (fun pair -> snd pair) nodes) let get_node (node, _, _) = node in
list_map get_node
(list_filter (fun (_, defined, _) -> defined)
nodes)
(** Return the list of nodes which were originally defined,
i.e. the nodes that were defined before calling restrict_defined. *)
let get_originally_defined_nodes (g: t) =
let (nodes, _) = get_nodes_and_edges g in
let get_node (node, _, _) = node in
list_map get_node
(list_filter
(fun (_, defined, disabled) -> defined || disabled)
nodes)
(** Return the path of the source file *) (** Return the path of the source file *)
let get_source (g: t) = let get_source (g: t) =
@ -299,7 +328,7 @@ let get_nLOC (g: t) =
(** [extend cg1 gc2] extends [cg1] in place with nodes and edges from [gc2]; undefined nodes become defined if at least one side is. *) (** [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 extend cg_old cg_new =
let nodes, edges = get_nodes_and_edges cg_new in let nodes, edges = get_nodes_and_edges cg_new in
list_iter (fun (node, defined) -> _add_node cg_old node defined) nodes; list_iter (fun (node, defined, disabled) -> _add_node cg_old node defined disabled) nodes;
list_iter (fun (nfrom, nto) -> add_edge cg_old nfrom nto) edges list_iter (fun (nfrom, nto) -> add_edge cg_old nfrom nto) edges
(** Begin support for serialization *) (** Begin support for serialization *)
@ -312,7 +341,11 @@ let load_from_file (filename : DB.filename) : t option =
match Serialization.from_file callgraph_serializer filename with match Serialization.from_file callgraph_serializer filename with
| None -> None | None -> None
| Some (source, nLOC, (nodes, edges)) -> | Some (source, nLOC, (nodes, edges)) ->
list_iter (fun (node, defined) -> if defined then add_node g node) nodes; list_iter
(fun (node, defined, disabled) ->
if defined then add_defined_node g node;
if disabled then add_disabled_node g node)
nodes;
list_iter (fun (nfrom, nto) -> add_edge g nfrom nto) edges; list_iter (fun (nfrom, nto) -> add_edge g nfrom nto) edges;
g.source <- source; g.source <- source;
g.nLOC <- nLOC; g.nLOC <- nLOC;

@ -21,34 +21,94 @@ type t (** the type of a call graph *)
(** A call graph consists of a set of nodes (Procname.t), and edges between them. (** A call graph consists of a set of nodes (Procname.t), and edges between them.
A node can be defined or undefined (to represent whether we have code for it). A node can be defined or undefined (to represent whether we have code for it).
In an edge from [n1] to [n2], indicating that [n1] calls [n2], [n1] is the parent and [n2] is the child. In an edge from [n1] to [n2], indicating that [n1] calls [n2],
Node [n1] is dependent on [n2] if there is a path from [n1] to [n2] using the child relationship. *) [n1] is the parent and [n2] is the child.
Node [n1] is dependent on [n2] if there is a path from [n1] to [n2]
using the child relationship. *)
(** [add_edge cg f t] adds an edge from [f] to [t] in the call graph [cg]. The nodes are also added as undefined, unless already present. *) (** [add_edge cg f t] adds an edge from [f] to [t] in the call graph [cg].
The nodes are also added as undefined, unless already present. *)
val add_edge : t -> Procname.t -> Procname.t -> unit val add_edge : t -> Procname.t -> Procname.t -> unit
val add_node : t -> Procname.t -> unit (** Add a node to the call graph as defined *) (** Add a node to the call graph as defined *)
val calls_recursively: t -> Procname.t -> Procname.t -> bool (** [calls_recursively g source dest] returns [true] if function [source] recursively calls function [dest] *) val add_defined_node : t -> Procname.t -> unit
val create : unit -> t (** Create an empty call graph *)
val extend : t -> t -> unit (** [extend cg1 gc2] extends [cg1] in place with nodes and edges from [gc2]; undefined nodes become defined if at least one side is. *) (** [calls_recursively g source dest] returns [true] if function [source]
val get_all_children : t -> Procname.t -> Procname.Set.t (** Return all the children of [n], whether defined or not *) recursively calls function [dest] *)
val get_ancestors : t -> Procname.t -> Procname.Set.t (** Compute the ancestors of the node, if not pre-computed already *) val calls_recursively: t -> Procname.t -> Procname.t -> bool
val get_heirs : t -> Procname.t -> Procname.Set.t (** Compute the heirs of the node, if not pre-computed already *)
val get_calls : t -> Procname.t -> in_out_calls (** Return the in/out calls of the node *) (** Create an empty call graph *)
val get_defined_nodes : t -> Procname.t list (** Return the list of nodes which are defined *) val create : unit -> t
val get_defined_children: t -> Procname.t -> Procname.Set.t (** Return the children of [n] which are defined *)
val get_dependents: t -> Procname.t -> Procname.Set.t (** Return the nodes dependent on [n] *) (** [extend cg1 gc2] extends [cg1] in place with nodes and edges from [gc2];
val get_nLOC: t -> int (** Return the number of LOC of the source file *) undefined nodes become defined if at least one side is. *)
val get_nodes_and_calls : t -> (Procname.t * in_out_calls) list (** Return the list of nodes with calls *) val extend : t -> t -> unit
val get_nodes_and_defined_children : t -> (Procname.t * Procname.Set.t) list (** Return all the nodes with their defined children *)
val get_nodes_and_edges : t -> (Procname.t * bool) list * (Procname.t * Procname.t) list (** Return the list of nodes, with defined flag, and the list of edges *) (** Return all the children of [n], whether defined or not *)
val get_nonrecursive_dependents : t -> Procname.t -> Procname.Set.t (** Return the children of [n] which are not heirs of [n] and are defined *) val get_all_children : t -> Procname.t -> Procname.Set.t
val get_parents : t -> Procname.t -> Procname.Set.t (** Return the parents of [n] *)
val get_recursive_dependents: t -> Procname.t -> Procname.Set.t (** Return the ancestors of [n] which are also heirs of [n] *) (** Compute the ancestors of the node, if not pre-computed already *)
val get_source : t -> DB.source_file (** Return the path of the source file *) val get_ancestors : t -> Procname.t -> Procname.Set.t
val load_from_file : DB.filename -> t option (** Load a call graph from a file *)
val node_defined : t -> Procname.t -> bool (** Returns true if the node is defined *) (** Compute the heirs of the node, if not pre-computed already *)
val restrict_defined : t -> Procname.Set.t option -> unit (** if not None, restrict defined nodes to the given set *) val get_heirs : t -> Procname.t -> Procname.Set.t
val save_call_graph_dotty : DB.filename option -> (Procname.t -> 'a list) -> t -> unit (** Print the current call graph as a dotty file. If the filename is [None], use the current file dir inside the DB dir. *)
val store_to_file : DB.filename -> t -> unit (** Save a call graph into a file *) (** Return the in/out calls of the node *)
val node_set_defined : t -> Procname.t -> bool -> unit (** Change the defined flag of a node *) val get_calls : t -> Procname.t -> in_out_calls
(** Return the list of nodes which are defined *)
val get_defined_nodes : t -> Procname.t list
(** Return the list of nodes which were originally defined,
i.e. the nodes that were defined before calling restrict_defined. *)
val get_originally_defined_nodes : t -> Procname.t list
(** Return the children of [n] which are defined *)
val get_defined_children: t -> Procname.t -> Procname.Set.t
(** Return the nodes dependent on [n] *)
val get_dependents: t -> Procname.t -> Procname.Set.t
(** Return the number of LOC of the source file *)
val get_nLOC: t -> int
(** Return the list of nodes with calls *)
val get_nodes_and_calls : t -> (Procname.t * in_out_calls) list
(** Return all the nodes with their defined children *)
val get_nodes_and_defined_children : t -> (Procname.t * Procname.Set.t) list
(** Return the list of nodes, with defined+disabled flag, and the list of edges *)
val get_nodes_and_edges : t -> (Procname.t * bool * bool) list * (Procname.t * Procname.t) list
(** Return the children of [n] which are not heirs of [n] and are defined *)
val get_nonrecursive_dependents : t -> Procname.t -> Procname.Set.t
(** Return the parents of [n] *)
val get_parents : t -> Procname.t -> Procname.Set.t
(** Return the ancestors of [n] which are also heirs of [n] *)
val get_recursive_dependents: t -> Procname.t -> Procname.Set.t
(** Return the path of the source file *)
val get_source : t -> DB.source_file
(** Load a call graph from a file *)
val load_from_file : DB.filename -> t option
(** Returns true if the node is defined *)
val node_defined : t -> Procname.t -> bool
(** If not None, restrict defined nodes to the given set,
and mark them as disabled. *)
val restrict_defined : t -> Procname.Set.t option -> unit
(** Print the current call graph as a dotty file. If the filename is [None],
use the current file dir inside the DB dir. *)
val save_call_graph_dotty : DB.filename option -> (Procname.t -> 'a list) -> t -> unit
(** Save a call graph into a file *)
val store_to_file : DB.filename -> t -> unit
(** Change the defined flag of a node *)
val node_set_defined : t -> Procname.t -> bool -> unit

@ -477,9 +477,9 @@ let compute_clusters exe_env files_changed : Cluster.t list =
let global_cg = Exe_env.get_cg exe_env in let global_cg = Exe_env.get_cg exe_env in
let nodes, edges = Cg.get_nodes_and_edges global_cg in let nodes, edges = Cg.get_nodes_and_edges global_cg in
let defined_procs = Cg.get_defined_nodes global_cg in let defined_procs = Cg.get_defined_nodes global_cg in
let do_node (n, defined) = let do_node (n, defined, restricted) =
if defined then if defined then
Cg.add_node file_cg Cg.add_defined_node file_cg
(ClusterMakefile.source_file_to_pname (Exe_env.get_source exe_env n)) in (ClusterMakefile.source_file_to_pname (Exe_env.get_source exe_env n)) in
let do_edge (n1, n2) = let do_edge (n1, n2) =
if Cg.node_defined global_cg n1 && Cg.node_defined global_cg n2 then if Cg.node_defined global_cg n1 && Cg.node_defined global_cg n2 then

@ -1266,7 +1266,7 @@ let do_analysis exe_env =
let summaryfp = analyze_proc exe_env proc_name in let summaryfp = analyze_proc exe_env proc_name in
Specs.add_summary proc_name summaryfp; Specs.add_summary proc_name summaryfp;
let cg = Cg.create () in let cg = Cg.create () in
Cg.add_node cg proc_name; Cg.add_defined_node cg proc_name;
perform_transition exe_env cg proc_name; perform_transition exe_env cg proc_name;
let summaryre = analyze_proc exe_env proc_name in let summaryre = analyze_proc exe_env proc_name in
Specs.add_summary proc_name summaryre; Specs.add_summary proc_name summaryre;

@ -87,7 +87,7 @@ let do_analysis curr_pdesc proc_name =
Some (Cfg.Procdesc.get_attributes proc_desc) in Some (Cfg.Procdesc.get_attributes proc_desc) in
let call_graph = let call_graph =
let cg = Cg.create () in let cg = Cg.create () in
Cg.add_node cg proc_name; Cg.add_defined_node cg proc_name;
cg in cg in
Specs.reset_summary call_graph proc_name attributes_opt; Specs.reset_summary call_graph proc_name attributes_opt;
Specs.set_status proc_name Specs.ACTIVE in Specs.set_status proc_name Specs.ACTIVE in

@ -739,7 +739,7 @@ let initialize_map exe_env methods =
let collect_methods exe_env = let collect_methods exe_env =
let global_cg = Exe_env.get_cg exe_env in let global_cg = Exe_env.get_cg exe_env in
let nodes, edges = Cg.get_nodes_and_edges global_cg in let nodes, edges = Cg.get_nodes_and_edges global_cg in
let do_node (n, defined) defined_methods = let do_node (n, defined, restricted) defined_methods =
if defined then if defined then
Procname.Set.add n defined_methods Procname.Set.add n defined_methods
else defined_methods in else defined_methods in

@ -58,7 +58,7 @@ struct
let meth_body_nodes = T.instructions_trans context instrs extra_instrs exit_node in let meth_body_nodes = T.instructions_trans context instrs extra_instrs exit_node in
Cfg.Node.add_locals_ret_declaration start_node (Cfg.Procdesc.get_locals procdesc); Cfg.Node.add_locals_ret_declaration start_node (Cfg.Procdesc.get_locals procdesc);
Cfg.Node.set_succs_exn start_node meth_body_nodes []; Cfg.Node.set_succs_exn start_node meth_body_nodes [];
Cg.add_node (CContext.get_cg context) (Cfg.Procdesc.get_proc_name procdesc)) Cg.add_defined_node (CContext.get_cg context) (Cfg.Procdesc.get_proc_name procdesc))
| None -> ()) | None -> ())
with with
| Not_found -> () | Not_found -> ()

@ -252,7 +252,7 @@ let write_harness_to_file harness_instrs harness_file =
(** add the harness proc to the cg and make sure its callees can be looked up by sym execution *) (** add the harness proc to the cg and make sure its callees can be looked up by sym execution *)
let add_harness_to_cg harness_name harness_cfg harness_node loc cg tenv = let add_harness_to_cg harness_name harness_cfg harness_node loc cg tenv =
Cg.add_node cg harness_name; Cg.add_defined_node cg harness_name;
let create_dummy_procdesc proc_name = let create_dummy_procdesc proc_name =
(* convert a java type string to a type *) (* convert a java type string to a type *)
let rec lookup_typ typ_str = match typ_str with let rec lookup_typ typ_str = match typ_str with

@ -100,7 +100,7 @@ let add_cmethod never_null_matcher program icfg node cm is_static =
let method_body_nodes = Array.mapi (JTrans.instruction context) instrs in let method_body_nodes = Array.mapi (JTrans.instruction context) instrs in
let procname = Cfg.Procdesc.get_proc_name procdesc in let procname = Cfg.Procdesc.get_proc_name procdesc in
add_edges context start_node exn_node [exit_node] method_body_nodes impl false; add_edges context start_node exn_node [exit_node] method_body_nodes impl false;
Cg.add_node icfg.JContext.cg procname; Cg.add_defined_node icfg.JContext.cg procname;
if Procname.is_constructor procname then Cfg.set_procname_priority cfg procname if Procname.is_constructor procname then Cfg.set_procname_priority cfg procname
| JTrans.Called _ -> () | JTrans.Called _ -> ()
@ -115,7 +115,7 @@ let add_amethod program icfg node am is_static =
(* do not capture the method if there is a model for it *) (* do not capture the method if there is a model for it *)
JUtils.log "Skipping method with a model: %s@." (Procname.to_string (Cfg.Procdesc.get_proc_name procdesc)); JUtils.log "Skipping method with a model: %s@." (Procname.to_string (Cfg.Procdesc.get_proc_name procdesc));
| JTrans.Defined procdesc -> | JTrans.Defined procdesc ->
Cg.add_node icfg.JContext.cg (Cfg.Procdesc.get_proc_name procdesc) Cg.add_defined_node icfg.JContext.cg (Cfg.Procdesc.get_proc_name procdesc)
| JTrans.Called _ -> () | JTrans.Called _ -> ()

@ -156,7 +156,7 @@ let trans_function_def (cfg : Cfg.cfg) (cg: Cg.t) (metadata : LAst.metadata_map)
Cfg.Procdesc.set_exit_node procdesc exit_node; Cfg.Procdesc.set_exit_node procdesc exit_node;
link_nodes start_node nodes; link_nodes start_node nodes;
Cfg.Node.add_locals_ret_declaration start_node locals; Cfg.Node.add_locals_ret_declaration start_node locals;
Cg.add_node cg proc_name; Cg.add_defined_node cg proc_name;
list_iter (Cg.add_edge cg proc_name) (callees_of_function_def func_def) list_iter (Cg.add_edge cg proc_name) (callees_of_function_def func_def)
let trans_program : LAst.program -> Cfg.cfg * Cg.t * Sil.tenv = function let trans_program : LAst.program -> Cfg.cfg * Cg.t * Sil.tenv = function

Loading…
Cancel
Save