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.

186 lines
5.9 KiB

(*
* 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
module L = Logging
type throws =
| DontKnow (** May or may not throw an exception. *)
| Throws (** Definitely throws an exception. *)
| DoesNotThrow (** Does not throw an exception. *)
(** Module type used to define the state component for a dataflow algorithm. *)
module type DFStateType = sig
type t (** Type for state. *)
val equal : t -> t -> bool (** Equality between states. *)
val join : t -> t -> t (** Join two states (the old one is the first parameter). *)
(** Perform a state transition on a node. *)
val do_node : Cfg.Node.t -> t -> (t list) * (t list)
val proc_throws : Procname.t -> throws (** Can proc throw an exception? *)
end
(** Type for the dataflow API. *)
module type DF = sig
type t
type state
type transition =
| Dead_state
| Transition of state * state list * state list
val join : state list -> state -> state
val run : Cfg.Procdesc.t -> state -> (Cfg.Node.t -> transition)
end
(** Determine if the node can throw an exception. *)
let node_throws node (proc_throws : Procname.t -> throws) : throws =
let instr_throws instr =
let pvar_is_return pvar =
let pdesc = Cfg.Node.get_proc_desc node in
let ret_pvar = Cfg.Procdesc.get_ret_var pdesc in
Sil.pvar_equal pvar ret_pvar in
match instr with
| Sil.Set (Sil.Lvar pvar, typ, Sil.Const (Sil.Cexn _), loc) when pvar_is_return pvar ->
(* assignment to return variable is an artifact of a throw instruction *)
Throws
| Sil.Call (_, Sil.Const (Sil.Cfun callee_pn), args, loc, _)
when SymExec.function_is_builtin callee_pn ->
if Procname.equal callee_pn SymExec.ModelBuiltins.__cast
then DontKnow
else DoesNotThrow
| Sil.Call (_, Sil.Const (Sil.Cfun callee_pn), args, loc, _) ->
proc_throws callee_pn
| _ ->
DoesNotThrow in
let res = ref DoesNotThrow in
let update_res throws = match !res, throws with
| DontKnow, DontKnow -> res := DontKnow
| Throws, _
| _, Throws -> res := Throws
| DoesNotThrow, t
| t, DoesNotThrow -> res := t in
let do_instr instr = update_res (instr_throws instr) in
list_iter do_instr (Cfg.Node.get_instrs node);
!res
(** Create an instance of the dataflow algorithm given a state parameter. *)
module MakeDF(St: DFStateType) : DF with type state = St.t = struct
module S = Cfg.NodeSet
module H = Cfg.NodeHash
module N = Cfg.Node
type worklist = S.t
type statemap = St.t H.t
type statelistmap = (St.t list) H.t
type t = {
mutable worklist: worklist;
pre_states : statemap;
post_states : statelistmap;
exn_states : statelistmap;
proc_desc : Cfg.Procdesc.t
}
type state = St.t
type transition =
| Dead_state
| Transition of state * state list * state list
let join states initial_state =
list_fold_left
St.join
initial_state
states
(** Propagate [new_state] to all the nodes immediately reachable. *)
let propagate t node states_succ states_exn (throws : throws) =
let propagate_to_dest new_state dest_node =
let push_state s =
H.replace t.pre_states dest_node s;
t.worklist <- S.add dest_node t.worklist in
try
let dest_state = H.find t.pre_states dest_node in
let dest_joined = St.join dest_state new_state in
if not (St.equal dest_state dest_joined) then
push_state dest_joined
with Not_found -> push_state new_state in
let succ_nodes = Cfg.Node.get_succs node in
let exn_nodes = Cfg.Node.get_exn node in
if throws <> Throws then
list_iter
(fun s -> list_iter (propagate_to_dest s) succ_nodes)
states_succ;
if throws <> DoesNotThrow then
list_iter
(fun s -> list_iter (propagate_to_dest s) exn_nodes)
states_exn;
H.replace t.post_states node states_succ;
H.replace t.exn_states node states_exn
(** Run the worklist-based dataflow algorithm. *)
let run proc_desc state =
let t =
let start_node = Cfg.Procdesc.get_start_node proc_desc in
let init_set = S.singleton start_node in
let init_statemap =
let m = H.create 1 in
H.replace m start_node state; m in
{
worklist = init_set;
pre_states = init_statemap;
post_states = H.create 0;
exn_states = H.create 0;
proc_desc = proc_desc
} in
let () =
while (not (S.is_empty t.worklist)) do
let node = S.min_elt t.worklist in
t.worklist <- S.remove node t.worklist;
try
let state = H.find t.pre_states node in
let states_succ, states_exn = St.do_node node state in
propagate t node states_succ states_exn (node_throws node St.proc_throws)
with Not_found -> ()
done in
let transitions node =
try
Transition
(H.find t.pre_states node, H.find t.post_states node, H.find t.exn_states node)
with Not_found -> Dead_state in
transitions
end (* MakeDF *)
(** Example dataflow callback: compute the the distance from a node to the start node. *)
let callback_test_dataflow all_procs get_proc_desc idenv tenv proc_name proc_desc =
let verbose = false in
let module DFCount = MakeDF(struct
type t = int
let equal = int_equal
let join n m = if n = 0 then m else n
let do_node n s =
if verbose then L.stdout "visiting node %a with state %d@." Cfg.Node.pp n s;
[s + 1], [s + 1]
let proc_throws pn = DoesNotThrow
end) in
let transitions = DFCount.run proc_desc 0 in
let do_node node =
match transitions node with
| DFCount.Transition (pre_state, _, _) -> ()
| DFCount.Dead_state -> () in
list_iter do_node (Cfg.Procdesc.get_nodes proc_desc)