|
|
|
(*
|
|
|
|
* 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! IStd
|
|
|
|
module Hashtbl = Caml.Hashtbl
|
|
|
|
module L = Logging
|
|
|
|
module F = Format
|
|
|
|
|
|
|
|
(* =============== START of module Node =============== *)
|
|
|
|
module Node = struct
|
|
|
|
type id = int [@@deriving compare]
|
|
|
|
|
|
|
|
let equal_id = [%compare.equal : id]
|
|
|
|
|
|
|
|
type nodekind =
|
|
|
|
| Start_node of Typ.Procname.t
|
|
|
|
| Exit_node of Typ.Procname.t
|
|
|
|
| Stmt_node of string
|
|
|
|
| Join_node
|
|
|
|
| Prune_node of bool * Sil.if_kind * string (** (true/false branch, if_kind, comment) *)
|
|
|
|
| Skip_node of string
|
|
|
|
[@@deriving compare]
|
|
|
|
|
|
|
|
let equal_nodekind = [%compare.equal : nodekind]
|
|
|
|
|
|
|
|
(** a node *)
|
|
|
|
type t =
|
|
|
|
{ id: id (** unique id of the node *)
|
|
|
|
; mutable dist_exit: int option (** distance to the exit node *)
|
|
|
|
; mutable exn: t list (** exception nodes in the cfg *)
|
|
|
|
; mutable instrs: Sil.instr list (** instructions for symbolic execution *)
|
|
|
|
; kind: nodekind (** kind of node *)
|
|
|
|
; loc: Location.t (** location in the source code *)
|
|
|
|
; mutable preds: t list (** predecessor nodes in the cfg *)
|
|
|
|
; pname_opt: Typ.Procname.t option (** name of the procedure the node belongs to *)
|
|
|
|
; mutable succs: t list (** successor nodes in the cfg *) }
|
|
|
|
|
|
|
|
let exn_handler_kind = Stmt_node "exception handler"
|
|
|
|
|
|
|
|
let exn_sink_kind = Stmt_node "exceptions sink"
|
|
|
|
|
|
|
|
let throw_kind = Stmt_node "throw"
|
|
|
|
|
|
|
|
let dummy pname_opt =
|
|
|
|
{ id= 0
|
|
|
|
; dist_exit= None
|
|
|
|
; instrs= []
|
|
|
|
; kind= Skip_node "dummy"
|
|
|
|
; loc= Location.dummy
|
|
|
|
; pname_opt
|
|
|
|
; succs= []
|
|
|
|
; preds= []
|
|
|
|
; exn= [] }
|
|
|
|
|
|
|
|
|
|
|
|
let compare node1 node2 = Int.compare node1.id node2.id
|
|
|
|
|
|
|
|
let hash node = Hashtbl.hash node.id
|
|
|
|
|
|
|
|
let equal = [%compare.equal : t]
|
|
|
|
|
|
|
|
(** Get the unique id of the node *)
|
|
|
|
let get_id node = node.id
|
|
|
|
|
|
|
|
let get_succs node = node.succs
|
|
|
|
|
|
|
|
type node = t
|
|
|
|
|
|
|
|
module NodeSet = Caml.Set.Make (struct
|
|
|
|
type t = node
|
|
|
|
|
|
|
|
let compare = compare
|
|
|
|
end)
|
|
|
|
|
|
|
|
module IdMap = Caml.Map.Make (struct
|
|
|
|
type t = id
|
|
|
|
|
|
|
|
let compare = compare_id
|
|
|
|
end)
|
|
|
|
|
|
|
|
let get_exn node = node.exn
|
|
|
|
|
|
|
|
(** Get the name of the procedure the node belongs to *)
|
|
|
|
let get_proc_name node =
|
|
|
|
match node.pname_opt with
|
|
|
|
| None ->
|
|
|
|
L.internal_error "get_proc_name: at node %d@\n" node.id ;
|
|
|
|
assert false
|
|
|
|
| Some pname ->
|
|
|
|
pname
|
|
|
|
|
|
|
|
|
|
|
|
(** Get the predecessors of the node *)
|
|
|
|
let get_preds node = node.preds
|
|
|
|
|
|
|
|
(** Get the node kind *)
|
|
|
|
let get_kind node = node.kind
|
|
|
|
|
|
|
|
(** Get the instructions to be executed *)
|
|
|
|
let get_instrs node = node.instrs
|
|
|
|
|
|
|
|
(** Get the location of the node *)
|
|
|
|
let get_loc n = n.loc
|
|
|
|
|
|
|
|
(** Get the source location of the last instruction in the node *)
|
|
|
|
let get_last_loc n =
|
|
|
|
n |> get_instrs |> List.last |> Option.value_map ~f:Sil.instr_get_loc ~default:n.loc
|
|
|
|
|
|
|
|
|
|
|
|
let pp_id f id = F.pp_print_int f id
|
|
|
|
|
|
|
|
let pp f node = pp_id f (get_id node)
|
|
|
|
|
|
|
|
let get_distance_to_exit node = node.dist_exit
|
|
|
|
|
|
|
|
(** Append the instructions to the list of instructions to execute *)
|
|
|
|
let append_instrs node instrs = if instrs <> [] then node.instrs <- node.instrs @ instrs
|
|
|
|
|
|
|
|
(** Add the instructions at the beginning of the list of instructions to execute *)
|
|
|
|
let prepend_instrs node instrs = node.instrs <- instrs @ node.instrs
|
|
|
|
|
|
|
|
(** Map and replace the instructions to be executed *)
|
|
|
|
let replace_instrs node ~f =
|
|
|
|
let instrs' = IList.map_changed node.instrs ~equal:phys_equal ~f in
|
|
|
|
if not (phys_equal instrs' node.instrs) then node.instrs <- instrs'
|
|
|
|
|
|
|
|
|
|
|
|
(** Add declarations for local variables and return variable to the node *)
|
|
|
|
let add_locals_ret_declaration node (proc_attributes: ProcAttributes.t) locals =
|
|
|
|
let loc = get_loc node in
|
|
|
|
let pname = proc_attributes.proc_name in
|
|
|
|
let ret_var =
|
|
|
|
let ret_type = proc_attributes.ret_type in
|
|
|
|
(Pvar.get_ret_pvar pname, ret_type)
|
|
|
|
in
|
|
|
|
let construct_decl (var_data: ProcAttributes.var_data) =
|
|
|
|
(Pvar.mk var_data.name pname, var_data.typ)
|
|
|
|
in
|
|
|
|
let ptl = ret_var :: List.map ~f:construct_decl locals in
|
|
|
|
let instr = Sil.Declare_locals (ptl, loc) in
|
|
|
|
prepend_instrs node [instr]
|
|
|
|
|
|
|
|
|
|
|
|
(** Print extended instructions for the node,
|
|
|
|
highlighting the given subinstruction if present *)
|
|
|
|
let pp_instrs pe0 ~sub_instrs instro fmt node =
|
|
|
|
if sub_instrs then
|
|
|
|
let pe =
|
|
|
|
match instro with None -> pe0 | Some instr -> Pp.extend_colormap pe0 (Obj.repr instr) Red
|
|
|
|
in
|
|
|
|
let instrs = get_instrs node in
|
|
|
|
Sil.pp_instr_list pe fmt instrs
|
|
|
|
else
|
|
|
|
let () =
|
|
|
|
match get_kind node with
|
|
|
|
| Stmt_node s ->
|
|
|
|
F.fprintf fmt "statements (%s)" s
|
|
|
|
| Prune_node (_, _, descr) ->
|
|
|
|
F.fprintf fmt "assume %s" descr
|
|
|
|
| Exit_node _ ->
|
|
|
|
F.pp_print_string fmt "exit"
|
|
|
|
| Skip_node s ->
|
|
|
|
F.fprintf fmt "skip (%s)" s
|
|
|
|
| Start_node _ ->
|
|
|
|
F.pp_print_string fmt "start"
|
|
|
|
| Join_node ->
|
|
|
|
F.pp_print_string fmt "join"
|
|
|
|
in
|
|
|
|
F.fprintf fmt " %a " Location.pp (get_loc node)
|
|
|
|
|
|
|
|
|
|
|
|
(** Dump extended instructions for the node *)
|
|
|
|
let d_instrs ~(sub_instrs: bool) (curr_instr: Sil.instr option) (node: t) =
|
|
|
|
L.add_print_action (L.PTnode_instrs, Obj.repr (sub_instrs, curr_instr, node))
|
|
|
|
|
|
|
|
|
|
|
|
(** Return a description of the cfg node *)
|
|
|
|
let get_description pe node =
|
|
|
|
let str =
|
|
|
|
match get_kind node with
|
|
|
|
| Stmt_node _ ->
|
|
|
|
"Instructions"
|
|
|
|
| Prune_node (_, _, descr) ->
|
|
|
|
"Conditional" ^ " " ^ descr
|
|
|
|
| Exit_node _ ->
|
|
|
|
"Exit"
|
|
|
|
| Skip_node _ ->
|
|
|
|
"Skip"
|
|
|
|
| Start_node _ ->
|
|
|
|
"Start"
|
|
|
|
| Join_node ->
|
|
|
|
"Join"
|
|
|
|
in
|
|
|
|
let pp fmt = F.fprintf fmt "%s@.%a" str (pp_instrs pe None ~sub_instrs:true) node in
|
|
|
|
F.asprintf "%t" pp
|
|
|
|
end
|
|
|
|
|
|
|
|
(* =============== END of module Node =============== *)
|
|
|
|
|
|
|
|
(** Map over nodes *)
|
|
|
|
module NodeMap = Caml.Map.Make (Node)
|
|
|
|
|
|
|
|
(** Hash table with nodes as keys. *)
|
|
|
|
module NodeHash = Hashtbl.Make (Node)
|
|
|
|
|
|
|
|
(** Set of nodes. *)
|
|
|
|
module NodeSet = Node.NodeSet
|
|
|
|
|
|
|
|
(** Map with node id keys. *)
|
|
|
|
module IdMap = Node.IdMap
|
|
|
|
|
|
|
|
(** procedure description *)
|
|
|
|
type t =
|
|
|
|
{ attributes: ProcAttributes.t (** attributes of the procedure *)
|
|
|
|
; mutable nodes: Node.t list (** list of nodes of this procedure *)
|
|
|
|
; mutable nodes_num: int (** number of nodes *)
|
|
|
|
; mutable start_node: Node.t (** start node of this procedure *)
|
|
|
|
; mutable exit_node: Node.t (** exit node of this procedure *)
|
|
|
|
; mutable loop_heads: NodeSet.t option (** loop head nodes of this procedure *) }
|
|
|
|
[@@deriving compare]
|
|
|
|
|
|
|
|
let from_proc_attributes attributes =
|
|
|
|
let pname_opt = Some attributes.ProcAttributes.proc_name in
|
|
|
|
let start_node = Node.dummy pname_opt in
|
|
|
|
let exit_node = Node.dummy pname_opt in
|
|
|
|
{attributes; nodes= []; nodes_num= 0; start_node; exit_node; loop_heads= None}
|
|
|
|
|
|
|
|
|
|
|
|
(** Compute the distance of each node to the exit node, if not computed already *)
|
|
|
|
let compute_distance_to_exit_node pdesc =
|
|
|
|
let exit_node = pdesc.exit_node in
|
|
|
|
let rec mark_distance dist nodes =
|
|
|
|
let next_nodes = ref [] in
|
|
|
|
let do_node (node: Node.t) =
|
|
|
|
match node.dist_exit with
|
|
|
|
| Some _ ->
|
|
|
|
()
|
|
|
|
| None ->
|
|
|
|
node.dist_exit <- Some dist ;
|
|
|
|
next_nodes := node.preds @ !next_nodes
|
|
|
|
in
|
|
|
|
List.iter ~f:do_node nodes ;
|
|
|
|
if !next_nodes <> [] then mark_distance (dist + 1) !next_nodes
|
|
|
|
in
|
|
|
|
mark_distance 0 [exit_node]
|
|
|
|
|
|
|
|
|
|
|
|
(** check or indicate if we have performed preanalysis on the CFG *)
|
|
|
|
let did_preanalysis pdesc = pdesc.attributes.did_preanalysis
|
|
|
|
|
|
|
|
let signal_did_preanalysis pdesc = (pdesc.attributes).did_preanalysis <- true
|
|
|
|
|
|
|
|
let get_attributes pdesc = pdesc.attributes
|
|
|
|
|
|
|
|
let get_exit_node pdesc = pdesc.exit_node
|
|
|
|
|
|
|
|
(** Return name and type of formal parameters *)
|
|
|
|
let get_formals pdesc = pdesc.attributes.formals
|
|
|
|
|
|
|
|
let get_loc pdesc = pdesc.attributes.loc
|
|
|
|
|
|
|
|
(** Return name and type of local variables *)
|
|
|
|
let get_locals pdesc = pdesc.attributes.locals
|
|
|
|
|
|
|
|
(** Return name and type of captured variables *)
|
|
|
|
let get_captured pdesc = pdesc.attributes.captured
|
|
|
|
|
|
|
|
(** Return the visibility attribute *)
|
|
|
|
let get_access pdesc = pdesc.attributes.access
|
|
|
|
|
|
|
|
let get_nodes pdesc = pdesc.nodes
|
|
|
|
|
|
|
|
let get_nodes_num pdesc = pdesc.nodes_num
|
|
|
|
|
|
|
|
let get_proc_name pdesc = pdesc.attributes.proc_name
|
|
|
|
|
|
|
|
(** Return the return type of the procedure *)
|
|
|
|
let get_ret_type pdesc = pdesc.attributes.ret_type
|
|
|
|
|
|
|
|
let get_ret_var pdesc = Pvar.mk Ident.name_return (get_proc_name pdesc)
|
|
|
|
|
|
|
|
let get_start_node pdesc = pdesc.start_node
|
|
|
|
|
|
|
|
(** Return [true] iff the procedure is defined, and not just declared *)
|
|
|
|
let is_defined pdesc = pdesc.attributes.is_defined
|
|
|
|
|
|
|
|
let is_java_synchronized pdesc = pdesc.attributes.is_java_synchronized_method
|
|
|
|
|
|
|
|
let iter_nodes f pdesc = List.iter ~f (get_nodes pdesc)
|
|
|
|
|
|
|
|
let iter_instrs f pdesc =
|
|
|
|
let do_node node = List.iter ~f:(fun i -> f node i) (Node.get_instrs node) in
|
|
|
|
iter_nodes do_node pdesc
|
|
|
|
|
|
|
|
|
|
|
|
let fold_nodes pdesc ~init ~f = List.fold ~f ~init (get_nodes pdesc)
|
|
|
|
|
|
|
|
let fold_instrs pdesc ~init ~f =
|
|
|
|
let fold_node acc node =
|
|
|
|
List.fold ~f:(fun acc instr -> f acc node instr) ~init:acc (Node.get_instrs node)
|
|
|
|
in
|
|
|
|
fold_nodes ~f:fold_node ~init pdesc
|
|
|
|
|
|
|
|
|
|
|
|
let find_map_nodes pdesc ~f = List.find_map ~f (get_nodes pdesc)
|
|
|
|
|
|
|
|
let find_map_instrs pdesc ~f =
|
|
|
|
let find_map_node node = List.find_map ~f (Node.get_instrs node) in
|
|
|
|
find_map_nodes ~f:find_map_node pdesc
|
|
|
|
|
|
|
|
|
|
|
|
let replace_instrs pdesc ~f =
|
|
|
|
let do_node node = Node.replace_instrs ~f node in
|
|
|
|
iter_nodes do_node pdesc
|
|
|
|
|
|
|
|
|
|
|
|
(** fold between two nodes or until we reach a branching structure *)
|
|
|
|
let fold_slope_range =
|
|
|
|
let rec aux node visited acc ~f =
|
|
|
|
let visited = NodeSet.add node visited in
|
|
|
|
let acc = f acc node in
|
|
|
|
match Node.get_succs node with
|
|
|
|
| [n] when not (NodeSet.mem n visited) ->
|
|
|
|
aux n visited acc ~f
|
|
|
|
| _ ->
|
|
|
|
acc
|
|
|
|
in
|
|
|
|
fun src_node dst_node ~init ~f -> aux src_node (NodeSet.singleton dst_node) init ~f
|
|
|
|
|
|
|
|
|
|
|
|
(** Set the exit node of the proc desc *)
|
|
|
|
let set_exit_node pdesc node = pdesc.exit_node <- node
|
|
|
|
|
|
|
|
(** Set the start node of the proc desc *)
|
|
|
|
let set_start_node pdesc node = pdesc.start_node <- node
|
|
|
|
|
|
|
|
(** Append the locals to the list of local variables *)
|
|
|
|
let append_locals pdesc new_locals =
|
|
|
|
(pdesc.attributes).locals <- pdesc.attributes.locals @ new_locals
|
|
|
|
|
|
|
|
|
|
|
|
let set_succs_exn_only (node: Node.t) exn = node.exn <- exn
|
|
|
|
|
|
|
|
(** Set the successor nodes and exception nodes, and build predecessor links *)
|
|
|
|
let set_succs_exn_base (node: Node.t) succs exn =
|
|
|
|
node.succs <- succs ;
|
|
|
|
node.exn <- exn ;
|
|
|
|
List.iter ~f:(fun (n: Node.t) -> n.preds <- node :: n.preds) succs
|
|
|
|
|
|
|
|
|
|
|
|
(** Create a new cfg node *)
|
|
|
|
let create_node pdesc loc kind instrs =
|
|
|
|
pdesc.nodes_num <- pdesc.nodes_num + 1 ;
|
|
|
|
let node_id = pdesc.nodes_num in
|
|
|
|
let node =
|
|
|
|
{ Node.id= node_id
|
|
|
|
; dist_exit= None
|
|
|
|
; instrs
|
|
|
|
; kind
|
|
|
|
; loc
|
|
|
|
; preds= []
|
|
|
|
; pname_opt= Some pdesc.attributes.proc_name
|
|
|
|
; succs= []
|
|
|
|
; exn= [] }
|
|
|
|
in
|
|
|
|
pdesc.nodes <- node :: pdesc.nodes ;
|
|
|
|
node
|
|
|
|
|
|
|
|
|
|
|
|
(** Set the successor and exception nodes.
|
|
|
|
If this is a join node right before the exit node, add an extra node in the middle,
|
|
|
|
otherwise nullify and abstract instructions cannot be added after a conditional. *)
|
|
|
|
let node_set_succs_exn pdesc (node: Node.t) succs exn =
|
|
|
|
match (node.kind, succs) with
|
|
|
|
| Join_node, [({Node.kind= Exit_node _} as exit_node)] ->
|
|
|
|
let kind = Node.Stmt_node "between_join_and_exit" in
|
|
|
|
let node' = create_node pdesc node.loc kind node.instrs in
|
|
|
|
set_succs_exn_base node [node'] exn ;
|
|
|
|
set_succs_exn_base node' [exit_node] exn
|
|
|
|
| _ ->
|
|
|
|
set_succs_exn_base node succs exn
|
|
|
|
|
|
|
|
|
|
|
|
(** Get loop heads for widening.
|
|
|
|
It collects all target nodes of back-edges in a depth-first
|
|
|
|
traversal.
|
|
|
|
*)
|
|
|
|
let get_loop_heads pdesc =
|
|
|
|
let rec set_loop_head_rec visited heads wl =
|
|
|
|
match wl with
|
|
|
|
| [] ->
|
|
|
|
heads
|
|
|
|
| (n, ancester) :: wl' ->
|
|
|
|
if NodeSet.mem n visited then
|
|
|
|
if NodeSet.mem n ancester then set_loop_head_rec visited (NodeSet.add n heads) wl'
|
|
|
|
else set_loop_head_rec visited heads wl'
|
|
|
|
else
|
|
|
|
let ancester = NodeSet.add n ancester in
|
|
|
|
let succs = List.append (Node.get_succs n) (Node.get_exn n) in
|
|
|
|
let works = List.map ~f:(fun m -> (m, ancester)) succs in
|
|
|
|
set_loop_head_rec (NodeSet.add n visited) heads (List.append works wl')
|
|
|
|
in
|
|
|
|
let start_wl = [(get_start_node pdesc, NodeSet.empty)] in
|
|
|
|
let lh = set_loop_head_rec NodeSet.empty NodeSet.empty start_wl in
|
|
|
|
pdesc.loop_heads <- Some lh ;
|
|
|
|
lh
|
|
|
|
|
|
|
|
|
|
|
|
let is_loop_head pdesc (node: Node.t) =
|
|
|
|
let lh = match pdesc.loop_heads with Some lh -> lh | None -> get_loop_heads pdesc in
|
|
|
|
NodeSet.mem node lh
|
|
|
|
|
|
|
|
|
|
|
|
let pp_var_attributes fmt attrs =
|
|
|
|
let pp_attribute fmt attr =
|
|
|
|
match attr with ProcAttributes.Modify_in_block -> Format.pp_print_string fmt "__block"
|
|
|
|
in
|
|
|
|
if List.is_empty attrs then () else F.fprintf fmt "(%a)" (Pp.seq ~sep:"," pp_attribute) attrs
|
|
|
|
|
|
|
|
|
|
|
|
let pp_local fmt (var_data: ProcAttributes.var_data) =
|
|
|
|
Format.fprintf fmt " %a:%a%a" Mangled.pp var_data.name (Typ.pp_full Pp.text) var_data.typ
|
|
|
|
pp_var_attributes var_data.attributes
|
|
|
|
|
|
|
|
|
|
|
|
let pp_locals_list fmt etl =
|
|
|
|
if List.is_empty etl then Format.pp_print_string fmt "None" else List.iter ~f:(pp_local fmt) etl
|
|
|
|
|
|
|
|
|
|
|
|
let pp_variable_list fmt etl =
|
|
|
|
if List.is_empty etl then Format.pp_print_string fmt "None"
|
|
|
|
else
|
|
|
|
List.iter
|
|
|
|
~f:(fun (id, ty) -> Format.fprintf fmt " %a:%a" Mangled.pp id (Typ.pp_full Pp.text) ty)
|
|
|
|
etl
|
|
|
|
|
|
|
|
|
|
|
|
let pp_objc_accessor fmt accessor =
|
|
|
|
match accessor with
|
|
|
|
| Some (ProcAttributes.Objc_getter field) ->
|
|
|
|
Format.fprintf fmt "Getter of %a, " (Typ.Struct.pp_field Pp.text) field
|
|
|
|
| Some (ProcAttributes.Objc_setter field) ->
|
|
|
|
Format.fprintf fmt "Setter of %a, " (Typ.Struct.pp_field Pp.text) field
|
|
|
|
| None ->
|
|
|
|
()
|
|
|
|
|
|
|
|
|
|
|
|
let pp_signature fmt pdesc =
|
|
|
|
let attributes = get_attributes pdesc in
|
|
|
|
let pname = get_proc_name pdesc in
|
|
|
|
let pname_string = Typ.Procname.to_string pname in
|
|
|
|
let defined_string = match is_defined pdesc with true -> "defined" | false -> "undefined" in
|
|
|
|
Format.fprintf fmt "@[%s [%s, Return type: %s, %aFormals: %a, Locals: %a" pname_string
|
|
|
|
defined_string
|
|
|
|
(Typ.to_string (get_ret_type pdesc))
|
|
|
|
pp_objc_accessor attributes.ProcAttributes.objc_accessor pp_variable_list (get_formals pdesc)
|
|
|
|
pp_locals_list (get_locals pdesc) ;
|
|
|
|
if not (List.is_empty (get_captured pdesc)) then
|
|
|
|
Format.fprintf fmt ", Captured: %a" pp_variable_list (get_captured pdesc) ;
|
|
|
|
let method_annotation = attributes.ProcAttributes.method_annotation in
|
|
|
|
if not (Annot.Method.is_empty method_annotation) then
|
|
|
|
Format.fprintf fmt ", Annotation: %a" (Annot.Method.pp pname_string) method_annotation ;
|
|
|
|
Format.fprintf fmt "]@]@;"
|
|
|
|
|
|
|
|
|
|
|
|
let is_specialized pdesc =
|
|
|
|
let attributes = get_attributes pdesc in
|
|
|
|
attributes.ProcAttributes.is_specialized
|
|
|
|
|
|
|
|
|
|
|
|
(* true if pvar is a captured variable of a cpp lambda or objc block *)
|
|
|
|
let is_captured_var procdesc pvar =
|
|
|
|
let procname = get_proc_name procdesc in
|
|
|
|
let pvar_name = Pvar.get_name pvar in
|
|
|
|
let pvar_local_matches (var_data: ProcAttributes.var_data) =
|
|
|
|
Mangled.equal var_data.name pvar_name
|
|
|
|
in
|
|
|
|
let pvar_matches (name, _) = Mangled.equal name pvar_name in
|
|
|
|
let is_captured_var_cpp_lambda =
|
|
|
|
match procname with
|
|
|
|
| Typ.Procname.ObjC_Cpp cpp_pname ->
|
|
|
|
(* var is captured if the procedure is a lambda and the var is not in the locals or formals *)
|
|
|
|
Typ.Procname.ObjC_Cpp.is_cpp_lambda cpp_pname
|
|
|
|
&& not
|
|
|
|
( List.exists ~f:pvar_local_matches (get_locals procdesc)
|
|
|
|
|| List.exists ~f:pvar_matches (get_formals procdesc) )
|
|
|
|
| _ ->
|
|
|
|
false
|
|
|
|
in
|
|
|
|
let is_captured_var_objc_block =
|
|
|
|
(* var is captured if the procedure is a objc block and the var is in the captured *)
|
|
|
|
Typ.Procname.is_objc_block procname && List.exists ~f:pvar_matches (get_captured procdesc)
|
|
|
|
in
|
|
|
|
is_captured_var_cpp_lambda || is_captured_var_objc_block
|
|
|
|
|
|
|
|
|
|
|
|
let has_modify_in_block_attr procdesc pvar =
|
|
|
|
let pvar_name = Pvar.get_name pvar in
|
|
|
|
let pvar_local_matches (var_data: ProcAttributes.var_data) =
|
|
|
|
Mangled.equal var_data.name pvar_name
|
|
|
|
&& List.exists var_data.attributes ~f:(fun attr ->
|
|
|
|
ProcAttributes.var_attribute_equal attr ProcAttributes.Modify_in_block )
|
|
|
|
in
|
|
|
|
List.exists ~f:pvar_local_matches (get_locals procdesc)
|
|
|
|
|
|
|
|
|
|
|
|
(** Applies f_instr_list to all the instructions in all the nodes of the cfg *)
|
|
|
|
let convert_cfg ~callee_pdesc ~resolved_pdesc ~f_instr_list =
|
|
|
|
let resolved_pname = get_proc_name resolved_pdesc
|
|
|
|
and callee_start_node = get_start_node callee_pdesc
|
|
|
|
and callee_exit_node = get_exit_node callee_pdesc in
|
|
|
|
let convert_node_kind = function
|
|
|
|
| Node.Start_node _ ->
|
|
|
|
Node.Start_node resolved_pname
|
|
|
|
| Node.Exit_node _ ->
|
|
|
|
Node.Exit_node resolved_pname
|
|
|
|
| node_kind ->
|
|
|
|
node_kind
|
|
|
|
in
|
|
|
|
let node_map = ref NodeMap.empty in
|
|
|
|
let rec convert_node node =
|
|
|
|
let loc = Node.get_loc node
|
|
|
|
and kind = convert_node_kind (Node.get_kind node)
|
|
|
|
and instrs = f_instr_list (Node.get_instrs node) in
|
|
|
|
create_node resolved_pdesc loc kind instrs
|
|
|
|
and loop callee_nodes =
|
|
|
|
match callee_nodes with
|
|
|
|
| [] ->
|
|
|
|
[]
|
|
|
|
| node :: other_node ->
|
|
|
|
let converted_node =
|
|
|
|
try NodeMap.find node !node_map with Caml.Not_found ->
|
|
|
|
let new_node = convert_node node
|
|
|
|
and successors = Node.get_succs node
|
|
|
|
and exn_nodes = Node.get_exn node in
|
|
|
|
node_map := NodeMap.add node new_node !node_map ;
|
|
|
|
if Node.equal node callee_start_node then set_start_node resolved_pdesc new_node ;
|
|
|
|
if Node.equal node callee_exit_node then set_exit_node resolved_pdesc new_node ;
|
|
|
|
node_set_succs_exn callee_pdesc new_node (loop successors) (loop exn_nodes) ;
|
|
|
|
new_node
|
|
|
|
in
|
|
|
|
converted_node :: loop other_node
|
|
|
|
in
|
|
|
|
ignore (loop [callee_start_node]) ;
|
|
|
|
resolved_pdesc
|
|
|
|
|
|
|
|
|
|
|
|
(** clone a procedure description and apply the type substitutions where
|
|
|
|
the parameters are used *)
|
|
|
|
let specialize_types_proc callee_pdesc resolved_pdesc substitutions =
|
|
|
|
let resolved_pname = get_proc_name resolved_pdesc in
|
|
|
|
let convert_pvar pvar = Pvar.mk (Pvar.get_name pvar) resolved_pname in
|
|
|
|
let mk_ptr_typ typename =
|
|
|
|
(* Only consider pointers from Java objects for now *)
|
|
|
|
Typ.mk (Tptr (Typ.mk (Tstruct typename), Typ.Pk_pointer))
|
|
|
|
in
|
|
|
|
let convert_exp = function
|
|
|
|
| Exp.Lvar origin_pvar ->
|
|
|
|
Exp.Lvar (convert_pvar origin_pvar)
|
|
|
|
| exp ->
|
|
|
|
exp
|
|
|
|
in
|
|
|
|
let subst_map = ref Ident.Map.empty in
|
|
|
|
let redirect_typename origin_id =
|
|
|
|
try Some (Ident.Map.find origin_id !subst_map) with Caml.Not_found -> None
|
|
|
|
in
|
|
|
|
let convert_instr = function
|
|
|
|
| Sil.Load
|
|
|
|
( id
|
|
|
|
, (Exp.Lvar origin_pvar as origin_exp)
|
|
|
|
, {Typ.desc= Tptr ({desc= Tstruct origin_typename}, Pk_pointer)}
|
|
|
|
, loc ) ->
|
|
|
|
let specialized_typname =
|
|
|
|
try Mangled.Map.find (Pvar.get_name origin_pvar) substitutions with Caml.Not_found ->
|
|
|
|
origin_typename
|
|
|
|
in
|
|
|
|
subst_map := Ident.Map.add id specialized_typname !subst_map ;
|
|
|
|
Some (Sil.Load (id, convert_exp origin_exp, mk_ptr_typ specialized_typname, loc))
|
|
|
|
| Sil.Load (id, (Exp.Var origin_id as origin_exp), ({Typ.desc= Tstruct _} as origin_typ), loc) ->
|
|
|
|
let updated_typ : Typ.t =
|
|
|
|
try Typ.mk ~default:origin_typ (Tstruct (Ident.Map.find origin_id !subst_map))
|
|
|
|
with Caml.Not_found -> origin_typ
|
|
|
|
in
|
|
|
|
Some (Sil.Load (id, convert_exp origin_exp, updated_typ, loc))
|
|
|
|
| Sil.Load (id, origin_exp, origin_typ, loc) ->
|
|
|
|
Some (Sil.Load (id, convert_exp origin_exp, origin_typ, loc))
|
|
|
|
| Sil.Store (assignee_exp, origin_typ, origin_exp, loc) ->
|
|
|
|
let set_instr =
|
|
|
|
Sil.Store (convert_exp assignee_exp, origin_typ, convert_exp origin_exp, loc)
|
|
|
|
in
|
|
|
|
Some set_instr
|
|
|
|
| Sil.Call
|
|
|
|
( return_ids
|
|
|
|
, Exp.Const (Const.Cfun (Typ.Procname.Java callee_pname_java))
|
|
|
|
, (Exp.Var id, _) :: origin_args
|
|
|
|
, loc
|
|
|
|
, call_flags )
|
|
|
|
when call_flags.CallFlags.cf_virtual && redirect_typename id <> None ->
|
|
|
|
let redirected_typename = Option.value_exn (redirect_typename id) in
|
|
|
|
let redirected_typ = mk_ptr_typ redirected_typename in
|
|
|
|
let redirected_pname =
|
|
|
|
Typ.Procname.replace_class (Typ.Procname.Java callee_pname_java) redirected_typename
|
|
|
|
in
|
|
|
|
let args =
|
|
|
|
let other_args = List.map ~f:(fun (exp, typ) -> (convert_exp exp, typ)) origin_args in
|
|
|
|
(Exp.Var id, redirected_typ) :: other_args
|
|
|
|
in
|
|
|
|
let call_instr =
|
|
|
|
Sil.Call (return_ids, Exp.Const (Const.Cfun redirected_pname), args, loc, call_flags)
|
|
|
|
in
|
|
|
|
Some call_instr
|
|
|
|
| Sil.Call (return_ids, origin_call_exp, origin_args, loc, call_flags) ->
|
|
|
|
let converted_args = List.map ~f:(fun (exp, typ) -> (convert_exp exp, typ)) origin_args in
|
|
|
|
let call_instr =
|
|
|
|
Sil.Call (return_ids, convert_exp origin_call_exp, converted_args, loc, call_flags)
|
|
|
|
in
|
|
|
|
Some call_instr
|
|
|
|
| Sil.Prune (origin_exp, loc, is_true_branch, if_kind) ->
|
|
|
|
Some (Sil.Prune (convert_exp origin_exp, loc, is_true_branch, if_kind))
|
|
|
|
| Sil.Declare_locals (typed_vars, loc) ->
|
|
|
|
let new_typed_vars =
|
|
|
|
List.map ~f:(fun (pvar, typ) -> (convert_pvar pvar, typ)) typed_vars
|
|
|
|
in
|
|
|
|
Some (Sil.Declare_locals (new_typed_vars, loc))
|
|
|
|
| Sil.Nullify _ | Abstract _ | Sil.Remove_temps _ ->
|
|
|
|
(* these are generated instructions that will be replaced by the preanalysis *)
|
|
|
|
None
|
|
|
|
in
|
|
|
|
let f_instr_list instrs = List.filter_map ~f:convert_instr instrs in
|
|
|
|
convert_cfg ~callee_pdesc ~resolved_pdesc ~f_instr_list
|
|
|
|
|
|
|
|
|
|
|
|
(** Creates a copy of a procedure description and a list of type substitutions of the form
|
|
|
|
(name, typ) where name is a parameter. The resulting proc desc is isomorphic but
|
|
|
|
all the type of the parameters are replaced in the instructions according to the list.
|
|
|
|
The virtual calls are also replaced to match the parameter types *)
|
|
|
|
let specialize_types callee_pdesc resolved_pname args =
|
|
|
|
let callee_attributes = get_attributes callee_pdesc in
|
|
|
|
let resolved_params, substitutions =
|
|
|
|
List.fold2_exn
|
|
|
|
~f:(fun (params, subts) (param_name, param_typ) (_, arg_typ) ->
|
|
|
|
match arg_typ.Typ.desc with
|
|
|
|
| Tptr ({desc= Tstruct typename}, Pk_pointer) ->
|
|
|
|
(* Replace the type of the parameter by the type of the argument *)
|
|
|
|
((param_name, arg_typ) :: params, Mangled.Map.add param_name typename subts)
|
|
|
|
| _ ->
|
|
|
|
((param_name, param_typ) :: params, subts) )
|
|
|
|
~init:([], Mangled.Map.empty) callee_attributes.formals args
|
|
|
|
in
|
|
|
|
let resolved_attributes =
|
|
|
|
{ callee_attributes with
|
|
|
|
formals= List.rev resolved_params
|
|
|
|
; proc_name= resolved_pname
|
|
|
|
; is_specialized= true
|
|
|
|
; err_log= Errlog.empty () }
|
|
|
|
in
|
|
|
|
Attributes.store resolved_attributes ;
|
|
|
|
let resolved_pdesc = from_proc_attributes resolved_attributes in
|
|
|
|
specialize_types_proc callee_pdesc resolved_pdesc substitutions
|
|
|
|
|
|
|
|
|
|
|
|
let specialize_with_block_args_instrs resolved_pdesc substitutions =
|
|
|
|
let resolved_pname = get_proc_name resolved_pdesc in
|
|
|
|
let convert_pvar pvar = Pvar.mk (Pvar.get_name pvar) resolved_pname in
|
|
|
|
let convert_exp exp =
|
|
|
|
match exp with
|
|
|
|
| Exp.Lvar origin_pvar ->
|
|
|
|
let new_pvar = convert_pvar origin_pvar in
|
|
|
|
Exp.Lvar new_pvar
|
|
|
|
| _ ->
|
|
|
|
exp
|
|
|
|
in
|
|
|
|
let convert_instr (instrs, id_map) instr =
|
|
|
|
let get_block_name_and_load_captured_vars_instrs block_var loc =
|
|
|
|
let block_name, extra_formals = Mangled.Map.find block_var substitutions in
|
|
|
|
let ids, id_exp_typs, load_instrs =
|
|
|
|
List.map extra_formals ~f:(fun (var, typ) ->
|
|
|
|
let id = Ident.create_fresh Ident.knormal in
|
|
|
|
let pvar = Pvar.mk var resolved_pname in
|
|
|
|
(id, (Exp.Var id, pvar, typ), Sil.Load (id, Exp.Lvar pvar, typ, loc)) )
|
|
|
|
|> List.unzip3
|
|
|
|
in
|
|
|
|
let remove_temps_instr = Sil.Remove_temps (ids, loc) in
|
|
|
|
(block_name, id_exp_typs, load_instrs, remove_temps_instr)
|
|
|
|
in
|
|
|
|
let convert_generic_call return_ids exp origin_args loc call_flags =
|
|
|
|
let converted_args = List.map ~f:(fun (exp, typ) -> (convert_exp exp, typ)) origin_args in
|
|
|
|
let call_instr = Sil.Call (return_ids, exp, converted_args, loc, call_flags) in
|
|
|
|
(call_instr :: instrs, id_map)
|
|
|
|
in
|
|
|
|
match instr with
|
|
|
|
| Sil.Load (id, Exp.Lvar block_param, _, _)
|
|
|
|
when Mangled.Map.mem (Pvar.get_name block_param) substitutions ->
|
|
|
|
let id_map = Ident.Map.add id (Pvar.get_name block_param) id_map in
|
|
|
|
(* we don't need the load the block param instruction anymore *)
|
|
|
|
(instrs, id_map)
|
|
|
|
| Sil.Load (id, origin_exp, origin_typ, loc) ->
|
|
|
|
(Sil.Load (id, convert_exp origin_exp, origin_typ, loc) :: instrs, id_map)
|
|
|
|
| Sil.Store (assignee_exp, origin_typ, Exp.Var id, loc) when Ident.Map.mem id id_map ->
|
|
|
|
let block_param = Ident.Map.find id id_map in
|
|
|
|
let block_name, id_exp_typs, load_instrs, remove_temps_instr =
|
|
|
|
get_block_name_and_load_captured_vars_instrs block_param loc
|
|
|
|
in
|
|
|
|
let closure = Exp.Closure {name= block_name; captured_vars= id_exp_typs} in
|
|
|
|
let instr = Sil.Store (assignee_exp, origin_typ, closure, loc) in
|
|
|
|
(remove_temps_instr :: instr :: load_instrs @ instrs, id_map)
|
|
|
|
| Sil.Store (assignee_exp, origin_typ, origin_exp, loc) ->
|
|
|
|
let set_instr =
|
|
|
|
Sil.Store (convert_exp assignee_exp, origin_typ, convert_exp origin_exp, loc)
|
|
|
|
in
|
|
|
|
(set_instr :: instrs, id_map)
|
|
|
|
| Sil.Call (return_ids, Exp.Var id, origin_args, loc, call_flags) -> (
|
|
|
|
try
|
|
|
|
let block_name, id_exp_typs, load_instrs, remove_temps_instr =
|
|
|
|
let block_var = Ident.Map.find id id_map in
|
|
|
|
get_block_name_and_load_captured_vars_instrs block_var loc
|
|
|
|
in
|
|
|
|
let call_instr =
|
|
|
|
let id_exps = List.map ~f:(fun (id, _, typ) -> (id, typ)) id_exp_typs in
|
|
|
|
let converted_args =
|
|
|
|
List.map ~f:(fun (exp, typ) -> (convert_exp exp, typ)) origin_args
|
|
|
|
in
|
|
|
|
Sil.Call
|
|
|
|
( return_ids
|
|
|
|
, Exp.Const (Const.Cfun block_name)
|
|
|
|
, id_exps @ converted_args
|
|
|
|
, loc
|
|
|
|
, call_flags )
|
|
|
|
in
|
|
|
|
let instrs = remove_temps_instr :: call_instr :: load_instrs @ instrs in
|
|
|
|
(instrs, id_map)
|
|
|
|
with Caml.Not_found ->
|
|
|
|
convert_generic_call return_ids (Exp.Var id) origin_args loc call_flags )
|
|
|
|
| Sil.Call (return_ids, origin_call_exp, origin_args, loc, call_flags) ->
|
|
|
|
convert_generic_call return_ids origin_call_exp origin_args loc call_flags
|
|
|
|
| Sil.Prune (origin_exp, loc, is_true_branch, if_kind) ->
|
|
|
|
(Sil.Prune (convert_exp origin_exp, loc, is_true_branch, if_kind) :: instrs, id_map)
|
|
|
|
| Sil.Declare_locals (typed_vars, loc) ->
|
|
|
|
let new_typed_vars =
|
|
|
|
List.map ~f:(fun (pvar, typ) -> (convert_pvar pvar, typ)) typed_vars
|
|
|
|
in
|
|
|
|
(Sil.Declare_locals (new_typed_vars, loc) :: instrs, id_map)
|
|
|
|
| Sil.Nullify _ | Abstract _ | Sil.Remove_temps _ ->
|
|
|
|
(* these are generated instructions that will be replaced by the preanalysis *)
|
|
|
|
(instrs, id_map)
|
|
|
|
in
|
|
|
|
let f_instr_list instrs =
|
|
|
|
let instrs, _ = List.fold ~f:convert_instr ~init:([], Ident.Map.empty) instrs in
|
|
|
|
List.rev instrs
|
|
|
|
in
|
|
|
|
f_instr_list
|
|
|
|
|
|
|
|
|
|
|
|
let append_no_duplicates_formals_and_annot =
|
|
|
|
Staged.unstage
|
|
|
|
(IList.append_no_duplicates ~cmp:(fun ((name1, _), _) ((name2, _), _) ->
|
|
|
|
Mangled.compare name1 name2 ))
|
|
|
|
|
|
|
|
|
|
|
|
let specialize_with_block_args callee_pdesc pname_with_block_args block_args =
|
|
|
|
let callee_attributes = get_attributes callee_pdesc in
|
|
|
|
(* Substitution from a block parameter to the block name and the new formals
|
|
|
|
that correspond to the captured variables *)
|
|
|
|
let substitutions : (Typ.Procname.t * (Mangled.t * Typ.t) list) Mangled.Map.t =
|
|
|
|
List.fold2_exn callee_attributes.formals block_args ~init:Mangled.Map.empty ~f:
|
|
|
|
(fun subts (param_name, _) block_arg_opt ->
|
|
|
|
match block_arg_opt with
|
|
|
|
| Some (cl: Exp.closure) ->
|
|
|
|
let formals_from_captured =
|
|
|
|
List.map
|
|
|
|
~f:(fun (_, var, typ) ->
|
|
|
|
(* Here we create fresh names for the new formals, based on the names of the captured
|
|
|
|
variables annotated with the name of the caller method *)
|
|
|
|
(Pvar.get_name_of_local_with_procname var, typ) )
|
|
|
|
cl.captured_vars
|
|
|
|
in
|
|
|
|
Mangled.Map.add param_name (cl.name, formals_from_captured) subts
|
|
|
|
| None ->
|
|
|
|
subts )
|
|
|
|
in
|
|
|
|
(* Extend formals with fresh variables for the captured variables of the block arguments,
|
|
|
|
without duplications. *)
|
|
|
|
let new_formals_blocks_captured_vars, extended_formals_annots =
|
|
|
|
let new_formals_blocks_captured_vars_with_annots =
|
|
|
|
let formals_annots =
|
|
|
|
List.zip_exn callee_attributes.formals (snd callee_attributes.method_annotation)
|
|
|
|
in
|
|
|
|
List.fold formals_annots ~init:[] ~f:(fun acc ((param_name, typ), annot) ->
|
|
|
|
try
|
|
|
|
let _, captured = Mangled.Map.find param_name substitutions in
|
|
|
|
append_no_duplicates_formals_and_annot acc
|
|
|
|
(List.map captured ~f:(fun captured_var -> (captured_var, Annot.Item.empty)))
|
|
|
|
with Caml.Not_found ->
|
|
|
|
append_no_duplicates_formals_and_annot acc [((param_name, typ), annot)] )
|
|
|
|
in
|
|
|
|
List.unzip new_formals_blocks_captured_vars_with_annots
|
|
|
|
in
|
|
|
|
let source_file_captured =
|
|
|
|
let pname = get_proc_name callee_pdesc in
|
|
|
|
match Attributes.find_file_capturing_procedure pname with
|
|
|
|
| Some (source_file, _) ->
|
|
|
|
source_file
|
|
|
|
| None ->
|
|
|
|
Logging.die InternalError
|
|
|
|
"specialize_with_block_args ahould only be called with defined procedures, but we \
|
|
|
|
cannot find the captured file of procname %a"
|
|
|
|
Typ.Procname.pp pname
|
|
|
|
in
|
|
|
|
let resolved_attributes =
|
|
|
|
{ callee_attributes with
|
|
|
|
proc_name= pname_with_block_args
|
|
|
|
; is_defined= true
|
|
|
|
; err_log= Errlog.empty ()
|
|
|
|
; formals= new_formals_blocks_captured_vars
|
|
|
|
; method_annotation= (fst callee_attributes.method_annotation, extended_formals_annots)
|
|
|
|
; source_file_captured }
|
|
|
|
in
|
|
|
|
Attributes.store resolved_attributes ;
|
|
|
|
let resolved_pdesc = from_proc_attributes resolved_attributes in
|
|
|
|
Logging.(debug Analysis Verbose) "signature of base method %a@." pp_signature callee_pdesc ;
|
|
|
|
Logging.(debug Analysis Verbose)
|
|
|
|
"signature of specialized method %a@." pp_signature resolved_pdesc ;
|
|
|
|
convert_cfg ~callee_pdesc ~resolved_pdesc
|
|
|
|
~f_instr_list:(specialize_with_block_args_instrs resolved_pdesc substitutions)
|
|
|
|
|
|
|
|
|
|
|
|
let is_connected proc_desc =
|
|
|
|
let is_exit_node n = match Node.get_kind n with Node.Exit_node _ -> true | _ -> false in
|
|
|
|
let is_between_join_and_exit_node n =
|
|
|
|
match Node.get_kind n with
|
|
|
|
| Node.Stmt_node "between_join_and_exit" | Node.Stmt_node "Destruction" -> (
|
|
|
|
match Node.get_succs n with [n'] when is_exit_node n' -> true | _ -> false )
|
|
|
|
| _ ->
|
|
|
|
false
|
|
|
|
in
|
|
|
|
let rec is_consecutive_join_nodes n visited =
|
|
|
|
match Node.get_kind n with
|
|
|
|
| Node.Join_node
|
|
|
|
-> (
|
|
|
|
if NodeSet.mem n visited then false
|
|
|
|
else
|
|
|
|
let succs = Node.get_succs n in
|
|
|
|
match succs with
|
|
|
|
| [n'] ->
|
|
|
|
is_consecutive_join_nodes n' (NodeSet.add n visited)
|
|
|
|
| _ ->
|
|
|
|
false )
|
|
|
|
| _ ->
|
|
|
|
is_between_join_and_exit_node n
|
|
|
|
in
|
|
|
|
let find_broken_node n =
|
|
|
|
let succs = Node.get_succs n in
|
|
|
|
let preds = Node.get_preds n in
|
|
|
|
match Node.get_kind n with
|
|
|
|
| Node.Start_node _ ->
|
|
|
|
if List.is_empty succs || not (List.is_empty preds) then Error `Other else Ok ()
|
|
|
|
| Node.Exit_node _ ->
|
|
|
|
if not (List.is_empty succs) || List.is_empty preds then Error `Other else Ok ()
|
|
|
|
| Node.Stmt_node _ | Node.Prune_node _ | Node.Skip_node _ ->
|
|
|
|
if List.is_empty succs || List.is_empty preds then Error `Other else Ok ()
|
|
|
|
| Node.Join_node ->
|
|
|
|
(* Join node has the exception that it may be without predecessors
|
|
|
|
and pointing to between_join_and_exit which points to an exit node.
|
|
|
|
This happens when the if branches end with a return.
|
|
|
|
Nested if statements, where all branches have return statements,
|
|
|
|
introduce a sequence of join nodes *)
|
|
|
|
if
|
|
|
|
(List.is_empty preds && not (is_consecutive_join_nodes n NodeSet.empty))
|
|
|
|
|| (not (List.is_empty preds) && List.is_empty succs)
|
|
|
|
then Error `Join
|
|
|
|
else Ok ()
|
|
|
|
in
|
|
|
|
(* unconnected nodes generated by Join nodes are expected *)
|
|
|
|
let skip_join_errors current_status node =
|
|
|
|
match find_broken_node node with
|
|
|
|
| Ok () ->
|
|
|
|
Ok current_status
|
|
|
|
| Error `Join ->
|
|
|
|
Ok (Some `Join)
|
|
|
|
| Error _ as other_error ->
|
|
|
|
other_error
|
|
|
|
in
|
|
|
|
match List.fold_result (get_nodes proc_desc) ~init:None ~f:skip_join_errors with
|
|
|
|
| Ok (Some `Join) ->
|
|
|
|
Error `Join
|
|
|
|
| Ok None ->
|
|
|
|
Ok ()
|
|
|
|
| Error _ as error ->
|
|
|
|
error
|