|
|
|
(*
|
|
|
|
* Copyright (c) 2018-present, Facebook, Inc.
|
|
|
|
*
|
|
|
|
* This source code is licensed under the MIT license found in the
|
|
|
|
* LICENSE file in the root directory of this source tree.
|
|
|
|
*)
|
|
|
|
|
|
|
|
open! IStd
|
|
|
|
module L = Logging
|
|
|
|
|
|
|
|
let nid_int n = (Procdesc.Node.get_id n :> int)
|
|
|
|
|
|
|
|
type edge_type = {source: Procdesc.Node.t; target: Procdesc.Node.t} [@@deriving compare]
|
|
|
|
|
|
|
|
(* Find back-edges by using Tarjan's DFS traversal *)
|
|
|
|
(* instead of marking, we keep track of the pred node we came from *)
|
|
|
|
let get_back_edges pdesc =
|
|
|
|
let rec aux visited back_edges wl =
|
|
|
|
match wl with
|
|
|
|
| [] ->
|
|
|
|
back_edges
|
|
|
|
| (n, pred, ancestors) :: wl' ->
|
|
|
|
if Procdesc.NodeSet.mem n visited then
|
|
|
|
if Procdesc.NodeSet.mem n ancestors then
|
|
|
|
let back_edges' =
|
|
|
|
match pred with
|
|
|
|
| Some n_parent ->
|
|
|
|
{source= n_parent; target= n} :: back_edges
|
|
|
|
| None ->
|
|
|
|
assert false
|
|
|
|
in
|
|
|
|
aux visited back_edges' wl'
|
|
|
|
else aux visited back_edges wl'
|
|
|
|
else
|
|
|
|
let ancestors = Procdesc.NodeSet.add n ancestors in
|
|
|
|
let works =
|
|
|
|
List.fold ~init:wl'
|
|
|
|
~f:(fun acc m -> (m, Some n, ancestors) :: acc)
|
|
|
|
(Procdesc.Node.get_succs n)
|
|
|
|
in
|
|
|
|
aux (Procdesc.NodeSet.add n visited) back_edges works
|
|
|
|
in
|
|
|
|
let start_wl = [(Procdesc.get_start_node pdesc, None, Procdesc.NodeSet.empty)] in
|
|
|
|
aux Procdesc.NodeSet.empty [] start_wl
|
|
|
|
|
|
|
|
|
|
|
|
(* Get a set of nodes, `exit_nodes`, that themselves are not in the loop but their predecessors are
|
|
|
|
|
|
|
|
Visually:
|
|
|
|
|
|
|
|
target
|
|
|
|
/|
|
|
|
|
/ .
|
|
|
|
/ |
|
|
|
|
. node_in_loop
|
|
|
|
. |\
|
|
|
|
. . \
|
|
|
|
. . exit_node
|
|
|
|
\ .
|
|
|
|
\ |
|
|
|
|
\|
|
|
|
|
source
|
|
|
|
|
|
|
|
Often, exit_node is a prune node. *)
|
|
|
|
let get_exit_nodes_in_loop loop_nodes =
|
|
|
|
let succs_of_loop_nodes =
|
|
|
|
Control.GuardNodes.fold
|
|
|
|
(fun n acc ->
|
|
|
|
Procdesc.Node.get_succs n |> Control.GuardNodes.of_list |> Control.GuardNodes.union acc )
|
|
|
|
loop_nodes Control.GuardNodes.empty
|
|
|
|
in
|
|
|
|
Control.GuardNodes.diff succs_of_loop_nodes loop_nodes |> Control.GuardNodes.elements
|
|
|
|
|
|
|
|
|
|
|
|
(* Starting from the start_nodes, find all the nodes upwards until the
|
|
|
|
target is reached, i.e picking up predecessors which have not been
|
|
|
|
already added to the found_nodes *)
|
|
|
|
let get_all_nodes_upwards_until target start_nodes =
|
|
|
|
let rec aux found_nodes = function
|
|
|
|
| [] ->
|
|
|
|
found_nodes
|
|
|
|
| node :: wl' ->
|
|
|
|
if Control.GuardNodes.mem node found_nodes then aux found_nodes wl'
|
|
|
|
else
|
|
|
|
let preds = Procdesc.Node.get_preds node in
|
|
|
|
aux (Control.GuardNodes.add node found_nodes) (List.append preds wl')
|
|
|
|
in
|
|
|
|
aux (Control.GuardNodes.singleton target) start_nodes
|
|
|
|
|
|
|
|
|
|
|
|
let is_prune node =
|
|
|
|
match Procdesc.Node.get_kind node with Procdesc.Node.Prune_node _ -> true | _ -> false
|
|
|
|
|
|
|
|
|
|
|
|
(* Get a pair of maps (exit_map, loop_head_to_guard_map) where
|
|
|
|
exit_map : exit_node -> loop_head set (i.e. target of the back edges)
|
|
|
|
loop_head_to_guard_map : loop_head -> guard_nodes and
|
|
|
|
guard_nodes contains the nodes that may affect the looping behavior, i.e.
|
|
|
|
occur in the guard of the loop conditional. *)
|
|
|
|
let get_control_maps cfg =
|
|
|
|
(* Since there could be multiple back-edges per loop, collect all
|
|
|
|
source nodes per loop head *)
|
|
|
|
(* loop_head (target of back-edges) --> source nodes *)
|
|
|
|
let loop_head_to_source_nodes_map =
|
|
|
|
get_back_edges cfg
|
|
|
|
|> List.fold ~init:Procdesc.NodeMap.empty ~f:(fun loop_head_to_source_list {source; target} ->
|
|
|
|
Procdesc.NodeMap.update target
|
|
|
|
(function Some source_list -> Some (source :: source_list) | None -> Some [source])
|
|
|
|
loop_head_to_source_list )
|
|
|
|
in
|
|
|
|
Procdesc.NodeMap.fold
|
|
|
|
(fun loop_head source_list Control.({exit_map; loop_head_to_guard_nodes}) ->
|
|
|
|
L.(debug Analysis Medium)
|
|
|
|
"Back-edge source list : [%a] --> loop_head: %i \n" (Pp.comma_seq Procdesc.Node.pp)
|
|
|
|
source_list (nid_int loop_head) ;
|
|
|
|
let loop_nodes = get_all_nodes_upwards_until loop_head source_list in
|
|
|
|
let exit_nodes = get_exit_nodes_in_loop loop_nodes in
|
|
|
|
L.(debug Analysis Medium) "Exit nodes: [%a]\n" (Pp.comma_seq Procdesc.Node.pp) exit_nodes ;
|
|
|
|
(* find all the prune nodes in the loop guard *)
|
|
|
|
let guard_prune_nodes =
|
|
|
|
get_all_nodes_upwards_until loop_head exit_nodes |> Control.GuardNodes.filter is_prune
|
|
|
|
in
|
|
|
|
let exit_map' =
|
|
|
|
(List.fold_left ~init:exit_map ~f:(fun exit_map_acc exit_node ->
|
|
|
|
Control.ExitNodeToLoopHeads.update exit_node
|
|
|
|
(function
|
|
|
|
| Some existing_loop_heads ->
|
|
|
|
Some (Control.LoopHeads.add loop_head existing_loop_heads)
|
|
|
|
| None ->
|
|
|
|
Some (Control.LoopHeads.singleton loop_head))
|
|
|
|
exit_map_acc ))
|
|
|
|
exit_nodes
|
|
|
|
in
|
|
|
|
let loop_head_to_guard_nodes' =
|
|
|
|
Control.LoopHeadToGuardNodes.update loop_head
|
|
|
|
(function
|
|
|
|
| Some existing_guard_nodes ->
|
|
|
|
Some (Control.GuardNodes.union existing_guard_nodes guard_prune_nodes)
|
|
|
|
| None ->
|
|
|
|
Some guard_prune_nodes)
|
|
|
|
loop_head_to_guard_nodes
|
|
|
|
in
|
|
|
|
Control.{exit_map= exit_map'; loop_head_to_guard_nodes= loop_head_to_guard_nodes'} )
|
|
|
|
loop_head_to_source_nodes_map
|
|
|
|
Control.
|
|
|
|
{ exit_map= Control.ExitNodeToLoopHeads.empty
|
|
|
|
; loop_head_to_guard_nodes= Control.LoopHeadToGuardNodes.empty }
|