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.

181 lines
6.8 KiB

(*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* 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
(** Remove pairs of prune nodes that are for the same condition, i.e. sibling of the same parent.
This is necessary to prevent picking unnecessary control variables in do-while like loops *)
let remove_prune_node_pairs exit_nodes guard_nodes =
let exit_nodes = Control.GuardNodes.of_list exit_nodes in
let except_exit_nodes = Control.GuardNodes.diff guard_nodes exit_nodes in
L.(debug Analysis Medium) "Except exit nodes: [%a]\n" Control.GuardNodes.pp except_exit_nodes ;
except_exit_nodes
|> Control.GuardNodes.filter (fun node ->
is_prune node
&& Procdesc.Node.get_siblings node |> Sequence.hd
|> Option.value_map ~default:false ~f:(fun sibling ->
not (Control.GuardNodes.mem sibling except_exit_nodes) ) )
|> Control.GuardNodes.union exit_nodes
(** 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 get_loop_head_to_source_nodes cfg =
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 )
(** 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 loop_head_to_source_nodes_map =
Procdesc.NodeMap.fold
(fun loop_head source_list
(Control.{exit_map; loop_head_to_guard_nodes}, loop_head_to_loop_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
|> remove_prune_node_pairs 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
let loop_head_to_loop_nodes' =
LoopInvariant.LoopHeadToLoopNodes.update loop_head
(function
| Some existing_loop_nodes ->
Some (LoopInvariant.LoopNodes.union existing_loop_nodes loop_nodes)
| None ->
Some loop_nodes )
loop_head_to_loop_nodes
in
let open Control in
( {exit_map= exit_map'; loop_head_to_guard_nodes= loop_head_to_guard_nodes'}
, loop_head_to_loop_nodes' ) )
loop_head_to_source_nodes_map
( Control.
{ exit_map= Control.ExitNodeToLoopHeads.empty
; loop_head_to_guard_nodes= Control.LoopHeadToGuardNodes.empty }
, LoopInvariant.LoopHeadToLoopNodes.empty )
let get_loop_control_maps cfg =
let loop_head_to_source_nodes_map = get_loop_head_to_source_nodes cfg in
get_control_maps loop_head_to_source_nodes_map