(* * 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