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.

688 lines
25 KiB

(*
* 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! Utils
(** Execution Paths *)
module L = Logging
module F = Format
(* =============== START of the Path module ===============*)
module Path : sig
(** type for paths *)
type t
type session = int
(** add a call with its sub-path, the boolean indicates whether the subtrace for the procedure should be included *)
val add_call : bool -> t -> Procname.t -> t -> t
(** check whether a path contains another path *)
val contains : t -> t -> bool
(** check wether the path contains the given position *)
val contains_position : t -> PredSymb.path_pos -> bool
(** Create the location trace of the path, up to the path position if specified *)
val create_loc_trace : t -> PredSymb.path_pos option -> Errlog.loc_trace
(** return the current node of the path *)
val curr_node : t -> Procdesc.Node.t option
(** dump a path *)
val d : t -> unit
(** dump statistics of the path *)
val d_stats : t -> unit
(** extend a path with a new node reached from the given session, with an optional string for exceptions *)
val extend : Procdesc.Node.t -> Typename.t option -> session -> t -> t
(** extend a path with a new node reached from the given session, with an optional string for exceptions *)
val add_description : t -> string -> t
(** iterate over each node in the path, excluding calls, once *)
val iter_all_nodes_nocalls : (Procdesc.Node.t -> unit) -> t -> unit
val iter_shortest_sequence :
(int -> t -> int -> Typename.t option -> unit) -> PredSymb.path_pos option -> t -> unit
(** join two paths *)
val join : t -> t -> t
(** pretty print a path *)
val pp : Format.formatter -> t -> unit
(** pretty print statistics of the path *)
val pp_stats : Format.formatter -> t -> unit
(** create a new path with given start node *)
val start : Procdesc.Node.t -> t
(*
(** equality for paths *)
val equal : t -> t -> bool
val get_description : t -> string option
*)
end = struct
type session = int [@@deriving compare]
type stats =
{ mutable max_length : int; (* length of the longest linear sequence *)
mutable linear_num : float; (* number of linear sequences described by the path *) }
(* type aliases for components of t values that compare should ignore *)
type _stats = stats
let compare__stats _ _ = 0
type _procname = Procname.t
let compare__procname _ _ = 0
type _string_option = string option
let compare__string_option _ _ = 0
type t =
(* INVARIANT: stats are always set to dummy_stats unless we are in the middle of a traversal *)
(* in particular: a new traversal cannot be initiated during an existing traversal *)
| Pstart of Procdesc.Node.t * _stats (** start node *)
| Pnode of Procdesc.Node.t * Typename.t option * session * t * _stats * _string_option
(** we got to [node] from last [session] perhaps propagating exception [exn_opt],
and continue with [path]. *)
| Pjoin of t * t * _stats (** join of two paths *)
| Pcall of t * _procname * t * _stats (** add a sub-path originating from a call *)
[@@deriving compare]
let get_dummy_stats () =
{ max_length = - 1;
linear_num = - 1.0 }
let get_description path =
match path with
| Pnode (_, _, _, _, _, descr_opt) ->
descr_opt
| _ -> None
let add_description path description =
let add_descr descr_option description =
match descr_option with
| Some descr -> descr^" "^description
| None -> description in
match path with
| Pnode (node, exn_opt, session, path, stats, descr_opt) ->
let description = add_descr descr_opt description in
Pnode (node, exn_opt, session, path, stats, Some description)
| _ -> path
let set_dummy_stats stats =
stats.max_length <- - 1;
stats.linear_num <- - 1.0
let rec curr_node = function
| Pstart (node, _) -> Some node
| Pnode (node, _, _, _, _, _) -> Some node
| Pcall(p1, _, _, _) -> curr_node p1
| Pjoin _ ->
None
let start node = Pstart (node, get_dummy_stats ())
let extend (node: Procdesc.Node.t) exn_opt session path =
Pnode (node, exn_opt, session, path, get_dummy_stats (), None)
let join p1 p2 =
Pjoin (p1, p2, get_dummy_stats ())
let add_call include_subtrace p pname p_sub =
if include_subtrace then Pcall(p, pname, p_sub, get_dummy_stats ())
else p
(** functions in this module either do not assume, or do not re-establish, the invariant on dummy
stats *)
module Invariant = struct
(** check whether a stats is the dummy stats *)
let stats_is_dummy stats =
stats.max_length == - 1
(** return the stats of the path, assumes that the stats are computed *)
let get_stats = function
| Pstart (_, stats) -> stats
| Pnode (_, _, _, _, stats, _) -> stats
| Pjoin (_, _, stats) -> stats
| Pcall (_, _, _, stats) -> stats
(** restore the invariant that all the stats are dummy, so the path is ready for another
traversal assumes that the stats are computed beforehand, and ensures that the invariant
holds afterwards *)
let rec reset_stats = function
| Pstart (_, stats) ->
if not (stats_is_dummy stats) then set_dummy_stats stats
| Pnode (_, _, _, path, stats, _) ->
if not (stats_is_dummy stats) then
begin
reset_stats path;
set_dummy_stats stats
end
| Pjoin (path1, path2, stats) ->
if not (stats_is_dummy stats) then
begin
reset_stats path1;
reset_stats path2;
set_dummy_stats stats
end
| Pcall (path1, _, path2, stats) ->
if not (stats_is_dummy stats) then
begin
reset_stats path1;
reset_stats path2;
set_dummy_stats stats
end
(** Iterate [f] over the path and compute the stats, assuming the invariant: all the stats are
dummy. Function [f] (typically with side-effects) is applied once to every node, and
max_length in the stats is the length of a longest sequence of nodes in the path where [f]
returned [true] on at least one node. max_length is 0 if the path was visited but no node
satisfying [f] was found. Assumes that the invariant holds beforehand, and ensures that all
the stats are computed afterwards. Since this breaks the invariant, it must be followed by
reset_stats. *)
let rec compute_stats do_calls (f : Procdesc.Node.t -> bool) =
let nodes_found stats = stats.max_length > 0 in
function
| Pstart (node, stats) ->
if stats_is_dummy stats then
begin
let found = f node in
stats.max_length <- if found then 1 else 0;
stats.linear_num <- 1.0;
end
| Pnode (node, _, _, path, stats, _) ->
if stats_is_dummy stats then
begin
compute_stats do_calls f path;
let stats1 = get_stats path in
let found = f node || nodes_found stats1 (* the order is important as f has side-effects *) in
stats.max_length <- if found then 1 + stats1.max_length else 0;
stats.linear_num <- stats1.linear_num;
end
| Pjoin (path1, path2, stats) ->
if stats_is_dummy stats then
begin
compute_stats do_calls f path1;
compute_stats do_calls f path2;
let stats1, stats2 = get_stats path1, get_stats path2 in
stats.max_length <- max stats1.max_length stats2.max_length;
stats.linear_num <- stats1.linear_num +. stats2.linear_num
end
| Pcall (path1, _, path2, stats) ->
if stats_is_dummy stats then
begin
let stats2 = match do_calls with
| true ->
compute_stats do_calls f path2;
get_stats path2
| false ->
{ max_length = 0;
linear_num = 0.0 } in
let stats1 =
let f' =
if nodes_found stats2
then fun _ -> true (* already found in call, no need to search before the call *)
else f in
compute_stats do_calls f' path1;
get_stats path1 in
stats.max_length <- stats1.max_length + stats2.max_length;
stats.linear_num <- stats1.linear_num;
end
end (* End of module Invariant *)
(** iterate over each node in the path, excluding calls, once *)
let iter_all_nodes_nocalls f path =
Invariant.compute_stats false (fun node -> f node; true) path;
Invariant.reset_stats path
let get_path_pos node =
let pn = Procdesc.Node.get_proc_name node in
let n_id = Procdesc.Node.get_id node in
(pn, (n_id :> int))
let contains_position path pos =
let found = ref false in
let f node =
if PredSymb.equal_path_pos (get_path_pos node) pos then found := true;
true in
Invariant.compute_stats true f path;
Invariant.reset_stats path;
!found
(** iterate over the longest sequence belonging to the path,
restricting to those where [filter] holds of some element.
If a node is reached via an exception,
pass the exception information to [f] on the previous node *)
let iter_shortest_sequence_filter
(f : int -> t -> int -> Typename.t option -> unit)
(filter: Procdesc.Node.t -> bool) (path: t) : unit =
let rec doit level session path prev_exn_opt = match path with
| Pstart _ -> f level path session prev_exn_opt
| Pnode (_, exn_opt, session', p, _, _) ->
(* no two consecutive exceptions *)
let next_exn_opt = if prev_exn_opt <> None then None else exn_opt in
doit level (session' :> int) p next_exn_opt;
f level path session prev_exn_opt
| Pjoin (p1, p2, _) ->
if (Invariant.get_stats p1).max_length <= (Invariant.get_stats p2).max_length then
doit level session p1 prev_exn_opt
else
doit level session p2 prev_exn_opt
| Pcall (p1, _, p2, _) ->
let next_exn_opt = None in (* exn must already be inside the call *)
doit level session p1 next_exn_opt;
doit (level +1) session p2 next_exn_opt in
Invariant.compute_stats true filter path;
doit 0 0 path None;
Invariant.reset_stats path
(** iterate over the shortest sequence belonging to the path,
restricting to those containing the given position if given.
Do not iterate past the last occurrence of the given position.
[f level path session exn_opt] is passed the current nesting [level] and [path]
and previous [session] and possible exception [exn_opt] *)
let iter_shortest_sequence
(f : int -> t -> int -> Typename.t option -> unit)
(pos_opt : PredSymb.path_pos option) (path: t) : unit =
let filter node = match pos_opt with
| None -> true
| Some pos -> PredSymb.equal_path_pos (get_path_pos node) pos in
let path_pos_at_path p =
try
match curr_node p with
| Some node ->
pos_opt <> None && filter node
| None -> false
with exn when SymOp.exn_not_failure exn -> false in
let position_seen = ref false in
let inverse_sequence =
let log = ref [] in
let g level p session exn_opt =
if path_pos_at_path p then position_seen := true;
log := (level, p, session, exn_opt) :: !log in
iter_shortest_sequence_filter g filter path;
!log in
let sequence_up_to_last_seen =
if !position_seen then
let rec remove_until_seen = function
| ((_, p, _, _) as x):: l ->
if path_pos_at_path p then IList.rev (x :: l)
else remove_until_seen l
| [] -> [] in
remove_until_seen inverse_sequence
else IList.rev inverse_sequence in
IList.iter
(fun (level, p, session, exn_opt) -> f level p session exn_opt)
sequence_up_to_last_seen
(** return the node visited most, and number of visits, in the shortest linear sequence *)
let repetitions path =
let map = ref Procdesc.NodeMap.empty in
let add_node = function
| Some node ->
begin
try
let n = Procdesc.NodeMap.find node !map in
map := Procdesc.NodeMap.add node (n + 1) !map
with Not_found ->
map := Procdesc.NodeMap.add node 1 !map
end
| None ->
() in
iter_shortest_sequence (fun _ p _ _ -> add_node (curr_node p)) None path;
let max_rep_node = ref (Procdesc.Node.dummy ()) in
let max_rep_num = ref 0 in
Procdesc.NodeMap.iter
(fun node num -> if num > !max_rep_num then (max_rep_node := node; max_rep_num := num))
!map;
(!max_rep_node, !max_rep_num)
let stats_string path =
Invariant.compute_stats true (fun _ -> true) path;
let node, repetitions = repetitions path in
let str =
"linear paths: " ^ string_of_float (Invariant.get_stats path).linear_num ^
" max length: " ^ string_of_int (Invariant.get_stats path).max_length ^
" has repetitions: " ^ string_of_int repetitions ^
" of node " ^ (string_of_int (Procdesc.Node.get_id node :> int)) in
Invariant.reset_stats path;
str
let pp_stats fmt path =
F.fprintf fmt "%s" (stats_string path)
let d_stats path =
L.d_str (stats_string path)
module PathMap = Map.Make (struct
type nonrec t = t
let compare = compare
end)
let pp fmt path =
let delayed_num = ref 0 in
let delayed = ref PathMap.empty in
let add_path p =
try ignore (PathMap.find p !delayed) with Not_found ->
incr delayed_num;
delayed := PathMap.add p !delayed_num !delayed in
let path_seen p = (* path seen before *)
PathMap.mem p !delayed in
let rec add_delayed path =
if not (path_seen path) (* avoid exponential blowup *)
then match path with (* build a map from delayed paths to a unique number *)
| Pstart _ -> ()
| Pnode (_, _, _, p, _, _) -> add_delayed p
| Pjoin (p1, p2, _) | Pcall(p1, _, p2, _) -> (* delay paths occurring in a join *)
add_delayed p1;
add_delayed p2;
add_path p1;
add_path p2 in
let rec doit n fmt path =
try
if n > 0 then raise Not_found;
let num = PathMap.find path !delayed in
F.fprintf fmt "P%d" num
with Not_found ->
match path with
| Pstart (node, _) ->
F.fprintf fmt "n%a" Procdesc.Node.pp node
| Pnode (node, _, session, path, _, _) ->
F.fprintf fmt "%a(s%d).n%a" (doit (n - 1)) path (session :> int) Procdesc.Node.pp node
| Pjoin (path1, path2, _) ->
F.fprintf fmt "(%a + %a)" (doit (n - 1)) path1 (doit (n - 1)) path2
| Pcall (path1, _, path2, _) ->
F.fprintf fmt "(%a{%a})" (doit (n - 1)) path1 (doit (n - 1)) path2 in
let print_delayed () =
if not (PathMap.is_empty !delayed) then begin
let f path num = F.fprintf fmt "P%d = %a@\n" num (doit 1) path in
F.fprintf fmt "where@\n";
PathMap.iter f !delayed
end in
add_delayed path;
doit 0 fmt path;
print_delayed ()
let d p =
L.add_print_action (L.PTpath, Obj.repr p)
let rec contains p1 p2 = match p2 with
| Pjoin (p2', p2'', _) ->
contains p1 p2' || contains p1 p2''
| _ -> p1 == p2
let create_loc_trace path pos_opt : Errlog.loc_trace =
let trace = ref [] in
let g level path _ exn_opt =
match curr_node path with
| Some curr_node ->
begin
let curr_loc = Procdesc.Node.get_loc curr_node in
match Procdesc.Node.get_kind curr_node with
| Procdesc.Node.Join_node -> () (* omit join nodes from error traces *)
| Procdesc.Node.Start_node pname ->
let name = Procname.to_string pname in
let name_id = Procname.to_filename pname in
let descr = "start of procedure " ^ (Procname.to_simplified_string pname) in
let node_tags =
[(Io_infer.Xml.tag_kind,"procedure_start");
(Io_infer.Xml.tag_name, name);
(Io_infer.Xml.tag_name_id, name_id)] in
trace := Errlog.make_trace_element level curr_loc descr node_tags :: !trace
| Procdesc.Node.Prune_node (is_true_branch, if_kind, _) ->
let descr = match is_true_branch, if_kind with
| true, Sil.Ik_if -> "Taking true branch"
| false, Sil.Ik_if -> "Taking false branch"
| true, (Sil.Ik_for | Sil.Ik_while | Sil.Ik_dowhile) ->
"Loop condition is true. Entering loop body"
| false, (Sil.Ik_for | Sil.Ik_while | Sil.Ik_dowhile) ->
"Loop condition is false. Leaving loop"
| true, Sil.Ik_switch -> "Switch condition is true. Entering switch case"
| false, Sil.Ik_switch -> "Switch condition is false. Skipping switch case"
| true, (Sil.Ik_bexp | Sil.Ik_land_lor) -> "Condition is true"
| false, (Sil.Ik_bexp | Sil.Ik_land_lor) -> "Condition is false" in
let node_tags =
[(Io_infer.Xml.tag_kind,"condition");
(Io_infer.Xml.tag_branch, if is_true_branch then "true" else "false")] in
trace := Errlog.make_trace_element level curr_loc descr node_tags :: !trace
| Procdesc.Node.Exit_node pname ->
let descr = "return from a call to " ^ (Procname.to_string pname) in
let name = Procname.to_string pname in
let name_id = Procname.to_filename pname in
let node_tags =
[(Io_infer.Xml.tag_kind,"procedure_end");
(Io_infer.Xml.tag_name, name);
(Io_infer.Xml.tag_name_id, name_id)] in
trace := Errlog.make_trace_element level curr_loc descr node_tags :: !trace
| _ ->
let descr, node_tags =
match exn_opt with
| None -> "", []
| Some exn_name ->
let exn_str = Typename.name exn_name in
if exn_str = ""
then "exception", [(Io_infer.Xml.tag_kind,"exception")]
else
"exception " ^ exn_str,
[(Io_infer.Xml.tag_kind,"exception");
(Io_infer.Xml.tag_name, exn_str)] in
let descr =
match get_description path with
| Some path_descr ->
if String.length descr > 0 then descr^" "^path_descr else path_descr
| None -> descr in
trace := Errlog.make_trace_element level curr_loc descr node_tags :: !trace
end
| None ->
() in
iter_shortest_sequence g pos_opt path;
let compare lt1 lt2 =
let n = Int.compare lt1.Errlog.lt_level lt2.Errlog.lt_level in
if n <> 0 then n else Location.compare lt1.Errlog.lt_loc lt2.Errlog.lt_loc in
let relevant lt = lt.Errlog.lt_node_tags <> [] in
IList.remove_irrelevant_duplicates compare relevant (IList.rev !trace)
(* IList.remove_duplicates compare (IList.sort compare !trace) *)
(*
let equal p1 p2 =
compare p1 p2 = 0
*)
end
(* =============== END of the Path module ===============*)
module PropMap = Map.Make (struct
type t = Prop.normal Prop.t
let compare = Prop.compare_prop
end)
(* =============== START of the PathSet module ===============*)
module PathSet : sig
type t
(** It's the caller's resposibility to ensure that Prop.prop_rename_primed_footprint_vars was called on the prop *)
val add_renamed_prop : Prop.normal Prop.t -> Path.t -> t -> t
(** dump the pathset *)
val d : t -> unit
(** difference between two pathsets *)
val diff : t -> t -> t
(** empty pathset *)
val empty : t
(** list of elements in a pathset *)
val elements : t -> (Prop.normal Prop.t * Path.t) list
(** equality for pathsets *)
val equal : t -> t -> bool
(** filter a pathset on the prop component *)
val filter : (Prop.normal Prop.t -> bool) -> t -> t
(** find the list of props whose associated path contains the given path *)
val filter_path : Path.t -> t -> Prop.normal Prop.t list
(** fold over a pathset *)
val fold : (Prop.normal Prop.t -> Path.t -> 'a -> 'a) -> t -> 'a -> 'a
(** It's the caller's resposibility to ensure that Prop.prop_rename_primed_footprint_vars was called on the list *)
val from_renamed_list: (Prop.normal Prop.t * Path.t) list -> t
(** check whether the pathset is empty *)
val is_empty : t -> bool
(** iterate over a pathset *)
val iter : (Prop.normal Prop.t -> Path.t -> unit) -> t -> unit
(** map over the prop component of a pathset *)
val map : (Prop.normal Prop.t -> Prop.normal Prop.t) -> t -> t
(** map over the prop component of a pathset using a partial function; elements mapped to None are discarded *)
val map_option : (Prop.normal Prop.t -> Prop.normal Prop.t option) -> t -> t
(** partition a pathset on the prop component *)
val partition : (Prop.normal Prop.t -> bool) -> t -> t * t
(** pretty print the pathset *)
val pp : printenv -> Format.formatter -> t -> unit
(** number of elements in the pathset *)
val size : t -> int
(** convert to a list of props *)
val to_proplist : t -> Prop.normal Prop.t list
(** convert to a set of props *)
val to_propset : Tenv.t -> t -> Propset.t
(** union of two pathsets *)
val union : t -> t -> t
end = struct
type t = Path.t PropMap.t
let equal = PropMap.equal (fun _ _ -> true) (* only discriminate props, and ignore paths *)
let empty : t = PropMap.empty
let elements ps =
let plist = ref [] in
let f prop path = plist := (prop, path) :: !plist in
PropMap.iter f ps;
!plist
let to_proplist ps =
IList.map fst (elements ps)
let to_propset tenv ps =
Propset.from_proplist tenv (to_proplist ps)
let filter f ps =
let elements = ref [] in
PropMap.iter (fun p _ -> elements := p :: !elements) ps;
elements := IList.filter (fun p -> not (f p)) !elements;
let filtered_map = ref ps in
IList.iter (fun p -> filtered_map := PropMap.remove p !filtered_map) !elements;
!filtered_map
let partition f ps =
let elements = ref [] in
PropMap.iter (fun p _ -> elements := p :: !elements) ps;
let el1, el2 = ref ps, ref ps in
IList.iter (fun p -> if f p then el2 := PropMap.remove p !el2 else el1 := PropMap.remove p !el1) !elements;
!el1, !el2
(** It's the caller's resposibility to ensure that Prop.prop_rename_primed_footprint_vars was called on the prop *)
let add_renamed_prop (p: Prop.normal Prop.t) (path: Path.t) (ps: t) : t =
let path_new =
try
let path_old = PropMap.find p ps in
Path.join path_old path
with Not_found -> path in
PropMap.add p path_new ps
let union (ps1: t) (ps2: t) : t =
PropMap.fold add_renamed_prop ps1 ps2
(** check if the nodes in path p1 are a subset of those in p2 (not trace subset) *)
let path_nodes_subset p1 p2 =
let get_nodes p =
let s = ref Procdesc.NodeSet.empty in
Path.iter_all_nodes_nocalls (fun n -> s := Procdesc.NodeSet.add n !s) p;
!s in
Procdesc.NodeSet.subset (get_nodes p1) (get_nodes p2)
(** difference between pathsets for the differential fixpoint *)
let diff (ps1: t) (ps2: t) : t =
let res = ref ps1 in
let rem p path =
try
let path_old = PropMap.find p !res in
if path_nodes_subset path path_old (* do not propagate new path if it has no new nodes *)
then res := PropMap.remove p !res
with Not_found ->
res := PropMap.remove p !res in
PropMap.iter rem ps2;
!res
let is_empty = PropMap.is_empty
let iter = PropMap.iter
let fold = PropMap.fold
let map_option f ps =
let res = ref empty in
let do_elem prop path = match f prop with
| None -> ()
| Some prop' -> res := add_renamed_prop prop' path !res in
iter do_elem ps;
!res
let map f ps =
map_option (fun p -> Some (f p)) ps
let size ps =
let res = ref 0 in
let add _ _ = incr res in
let () = PropMap.iter add ps
in !res
let pp pe fmt ps =
let count = ref 0 in
let pp_path fmt path =
F.fprintf fmt "[path: %a@\n%a]" Path.pp_stats path Path.pp path in
let f prop path =
incr count;
F.fprintf fmt "PROP %d:%a@\n%a@\n" !count pp_path path (Prop.pp_prop pe) prop in
iter f ps
let d (ps: t) = L.add_print_action (L.PTpathset, Obj.repr ps)
let filter_path path ps =
let plist = ref [] in
let f prop path' =
if Path.contains path path'
then plist := prop :: !plist in
iter f ps;
!plist
(** It's the caller's resposibility to ensure that Prop.prop_rename_primed_footprint_vars was called on the list *)
let from_renamed_list (pl : ('a Prop.t * Path.t) list) : t =
IList.fold_left (fun ps (p, pa) -> add_renamed_prop p pa ps) empty pl
end
(* =============== END of the PathSet module ===============*)