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.

572 lines
23 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
module L = Logging
(** find all the predecessors of nodes, using exception links *)
module AllPreds = struct
module NodeHash = Cfg.NodeHash
let preds_table = NodeHash.create 3 (* table from node to set of predecessors *)
let clear_table () =
NodeHash.clear preds_table
let mk_table cfg =
let do_pdesc _ pdesc =
let exit_node = Cfg.Procdesc.get_exit_node pdesc in
let add_edge is_exn nfrom nto =
if is_exn && Cfg.Node.equal nto exit_node then ()
else
try
let preds = NodeHash.find preds_table nto in
let preds' = Cfg.NodeSet.add nfrom preds in
NodeHash.replace preds_table nto preds'
with Not_found ->
NodeHash.add preds_table nto (Cfg.NodeSet.singleton nfrom) in
let do_node n =
IList.iter (add_edge false n) (Cfg.Node.get_succs n);
IList.iter (add_edge true n) (Cfg.Node.get_exn n) in
let proc_nodes = Cfg.Procdesc.get_nodes pdesc in
IList.iter do_node proc_nodes in
clear_table ();
Cfg.iter_proc_desc cfg do_pdesc
let get_preds n =
try
let preds = NodeHash.find preds_table n in
Cfg.NodeSet.elements preds
with Not_found ->
Cfg.Node.get_preds n
end
module Vset = AddressTaken.PvarSet
let is_not_function cfg x =
let pname = Procname.from_string_c_fun (Mangled.to_string (Pvar.get_name x)) in
Cfg.Procdesc.find_from_name cfg pname = None
(** variables read in the expression *)
let rec use_exp cfg pdesc (exp: Sil.exp) acc =
match exp with
| Sil.Var _ | Sil.Sizeof _ -> acc
| Sil.Const (Cclosure { captured_vars; }) ->
IList.fold_left
(fun vset_acc (_, captured_pvar, _) -> Vset.add captured_pvar vset_acc)
acc
captured_vars
| Sil.Const
(Cint _ | Cfun _ | Cstr _ | Cfloat _ | Cattribute _ | Cexn _ | Cclass _ | Cptr_to_fld _) ->
acc
| Sil.Lvar x -> Vset.add x acc
| Sil.Cast (_, e) | Sil.UnOp (_, e, _) | Sil.Lfield (e, _, _) -> use_exp cfg pdesc e acc
| Sil.BinOp (_, e1, e2) | Sil.Lindex (e1, e2) -> use_exp cfg pdesc e1 (use_exp cfg pdesc e2 acc)
and use_etl cfg pdesc (etl: (Sil.exp * Sil.typ) list) acc =
IList.fold_left (fun acc (e, _) -> use_exp cfg pdesc e acc) acc etl
and use_instr cfg (pdesc: Cfg.Procdesc.t) (instr: Sil.instr) acc =
match instr with
| Sil.Set (_, _, e, _)
| Sil.Letderef (_, e, _, _) -> use_exp cfg pdesc e acc
| Sil.Prune (e, _, _, _) -> use_exp cfg pdesc e acc
| Sil.Call (_, _, etl, _, _) -> use_etl cfg pdesc etl acc
| Sil.Nullify _ -> acc
| Sil.Abstract _ | Sil.Remove_temps _ | Sil.Stackop _ | Sil.Declare_locals _ -> acc
(** variables written in the expression *)
let rec def_exp cfg (exp: Sil.exp) acc =
match exp with
| Sil.Lvar x -> if is_not_function cfg x then Vset.add x acc else acc
| Sil.Cast (_, e) -> def_exp cfg e acc
| _ -> acc
let rec def_instr cfg (instr: Sil.instr) acc =
match instr with
| Sil.Set (e, _, _, _) -> def_exp cfg e acc
| Sil.Call _ | Sil.Letderef _ | Sil.Prune _ -> acc
| Sil.Nullify (x, _, _) ->
if is_not_function cfg x then Vset.add x acc else acc
| Sil.Abstract _ | Sil.Remove_temps _ | Sil.Stackop _ | Sil.Declare_locals _ -> acc
and def_instrl cfg instrs acc =
IList.fold_left (fun acc' i -> def_instr cfg i acc') acc instrs
(** variables written by instructions in the node *)
let def_node cfg node acc =
match Cfg.Node.get_kind node with
| Cfg.Node.Start_node _ | Cfg.Node.Exit_node _ | Cfg.Node.Join_node | Cfg.Node.Skip_node _ -> acc
| Cfg.Node.Prune_node _
| Cfg.Node.Stmt_node _ ->
def_instrl cfg (Cfg.Node.get_instrs node) acc
let compute_live_instr cfg pdesc s instr =
use_instr cfg pdesc instr (Vset.diff s (def_instr cfg instr Vset.empty))
let compute_live_instrl cfg pdesc instrs livel =
IList.fold_left (compute_live_instr cfg pdesc) livel (IList.rev instrs)
module Worklist = struct
module S = Cfg.NodeSet
let worklist = ref S.empty
let reset _ = worklist := S.empty
let add node = worklist := S.add node !worklist
let pick () =
let min = S.min_elt !worklist in
worklist := S.remove min !worklist;
min
end
(** table of live variables *)
module Table: sig
val reset: unit -> unit
(** variables live after the last instruction in the current node *)
val get_live: Cfg.node -> Vset.t
(** propagate live variables to predecessor nodes *)
val propagate_to_preds: Vset.t -> Cfg.node list -> unit
val iter: Vset.t -> (Cfg.node -> Vset.t -> Vset.t -> unit) -> unit
end = struct
module H = Cfg.NodeHash
let table = H.create 1024
let reset _ = H.clear table
let get_live node = try H.find table node with Not_found -> Vset.empty
let replace node set = H.replace table node set
let propagate_to_preds set preds =
let do_node node =
try
let oldset = H.find table node in
let newset = Vset.union set oldset in
replace node newset;
if not (Vset.equal oldset newset) then Worklist.add node
with Not_found ->
replace node set; Worklist.add node in
IList.iter do_node preds
let iter init f =
let get_live_preds init node = (** nodes live at predecessors *)
match AllPreds.get_preds node with
| [] -> init
| preds -> IList.fold_left Vset.union Vset.empty (IList.map get_live preds) in
H.iter (fun node live -> f node (get_live_preds init node) live) table
end
(** Compute condidate nullable variables amongst formals and locals *)
let compute_candidates procdesc : Vset.t * (Vset.t -> Vset.elt list) =
let candidates = ref Vset.empty in
let struct_array_cand = ref Vset.empty in
let typ_is_struct_array = function
| Sil.Tstruct _ | Sil.Tarray _ -> true
| _ -> false in
let add_vi (pvar, typ) =
let pv = Pvar.mk pvar (Cfg.Procdesc.get_proc_name procdesc) in
candidates := Vset.add pv !candidates;
if typ_is_struct_array typ then struct_array_cand := Vset.add pv !struct_array_cand in
IList.iter add_vi (Cfg.Procdesc.get_formals procdesc);
IList.iter add_vi (Cfg.Procdesc.get_locals procdesc);
let get_sorted_candidates vs =
let priority, no_pri =
IList.partition
(fun pv -> Vset.mem pv !struct_array_cand)
(Vset.elements vs) in
IList.rev_append (IList.rev priority) no_pri in
!candidates, get_sorted_candidates
(** Construct a table wich associates to each node a set of live variables *)
let analyze_proc cfg pdesc cand =
let exit_node = Cfg.Procdesc.get_exit_node pdesc in
Worklist.reset ();
Table.reset ();
Worklist.add exit_node;
try
while true do
let node = Worklist.pick () in
let curr_live = Table.get_live node in
let preds = AllPreds.get_preds node in
let live_at_predecessors =
match Cfg.Node.get_kind node with
| Cfg.Node.Start_node _
| Cfg.Node.Exit_node _
| Cfg.Node.Join_node
| Cfg.Node.Skip_node _ -> curr_live
| Cfg.Node.Prune_node _
| Cfg.Node.Stmt_node _ ->
compute_live_instrl cfg pdesc (Cfg.Node.get_instrs node) curr_live in
Table.propagate_to_preds (Vset.inter live_at_predecessors cand) preds
done
with Not_found -> ()
(* Instruction i is nullifying a block variable *)
let is_block_nullify i =
match i with
| Sil.Nullify(pvar, _, true) -> Sil.is_block_pvar pvar
| _ -> false
(** Add nullify instructions to the node given dead program variables *)
let node_add_nullify_instrs n dead_vars_after dead_vars_before =
let loc = Cfg.Node.get_last_loc n in
let move_tmp_pvars_first pvars =
let pvars_tmp, pvars_notmp = IList.partition Errdesc.pvar_is_frontend_tmp pvars in
pvars_tmp @ pvars_notmp in
let instrs_after =
IList.map
(fun pvar -> Sil.Nullify (pvar, loc, false))
(move_tmp_pvars_first dead_vars_after) in
let instrs_before =
IList.map
(fun pvar -> Sil.Nullify (pvar, loc, false))
(move_tmp_pvars_first dead_vars_before) in
(* Nullify(bloc_var,_,true) can be placed in the middle
of the block because when we add this instruction*)
(* we don't have already all the instructions of the node.
Here we reorder the instructions to move *)
(* nullification of blocks at the end of existing instructions. *)
let block_nullify, no_block_nullify = IList.partition is_block_nullify (Cfg.Node.get_instrs n) in
Cfg.Node.replace_instrs n (no_block_nullify @ block_nullify);
Cfg.Node.append_instrs_temps n instrs_after [];
Cfg.Node.prepend_instrs_temps n instrs_before []
(** return true if the node does not assign any variables *)
let node_assigns_no_variables cfg node =
let instrs = Cfg.Node.get_instrs node in
let assign_set = def_instrl cfg instrs (Vset.empty) in
Vset.is_empty assign_set
(** Set the dead variables of a node, by default as dead_after.
If the node is a prune or a join node, propagate as dead_before in the successors *)
let add_dead_pvars_after_conditionals_join cfg n deads =
(* L.out " node %d: %a@." (Cfg.Node.get_id n) (Sil.pp_list pe_text) deads; *)
let seen = ref Cfg.NodeSet.empty in
let rec add_after_prune_join is_after node =
if Cfg.NodeSet.mem node !seen (* gone through a loop in the cfg *)
then Cfg.Node.set_dead_pvars n true deads
else
begin
seen := Cfg.NodeSet.add node !seen;
let node_is_exit n = match Cfg.Node.get_kind n with
| Cfg.Node.Exit_node _ -> true
| _ -> false in
let next_is_exit n = match Cfg.Node.get_succs n with
| [n'] -> node_is_exit n'
| _ -> false in
match Cfg.Node.get_kind node with
| Cfg.Node.Prune_node _
| Cfg.Node.Join_node
when node_assigns_no_variables cfg node && not (next_is_exit node) ->
(* cannot push nullify instructions after an assignment,
as they could nullify the same variable *)
let succs = Cfg.Node.get_succs node in
IList.iter (add_after_prune_join false) succs
| _ ->
let new_dead_pvs =
let old_pvs = Cfg.Node.get_dead_pvars node is_after in
let pv_is_new pv = not (IList.exists (Pvar.equal pv) old_pvs) in
(IList.filter pv_is_new deads) @ old_pvs in
Cfg.Node.set_dead_pvars node is_after new_dead_pvs
end in
add_after_prune_join true n
(** Find the set of dead variables for the procedure pname and add nullify instructions.
The variables whose addresses may be taken are only considered just before the exit node. *)
let analyze_and_annotate_proc cfg tenv pname pdesc =
let exit_node = Cfg.Procdesc.get_exit_node pdesc in
let exit_node_is_succ node =
match Cfg.Node.get_succs node with
| [en] -> Cfg.Node.equal en exit_node
| _ -> false in
let cand, get_sorted_cand = compute_candidates pdesc in
let addr_taken_vars =
if !Config.curr_language = Config.Java
then Vset.empty
else
match AddressTaken.Analyzer.compute_post (ProcData.make_default pdesc tenv) with
| Some post -> post
| None -> Vset.empty in
analyze_proc cfg pdesc cand;
let dead_pvars_added = ref 0 in
let dead_pvars_limit = 100000 in
let incr_dead_pvars_added pvars =
let num = IList.length pvars in
dead_pvars_added := num + !dead_pvars_added;
if !dead_pvars_added > dead_pvars_limit && !dead_pvars_added - num <= dead_pvars_limit
then
L.err "WARNING: liveness: more than %d dead pvars added in procedure %a, stopping@."
dead_pvars_limit Procname.pp pname in
Table.iter cand (fun n live_at_predecessors live_current -> (* set dead variables on nodes *)
(* live before, or assigned to *)
let nonnull_pvars = Vset.inter (def_node cfg n live_at_predecessors) cand in
(* only nullify when variables become live *)
let dead_pvars = Vset.diff nonnull_pvars live_current in
let dead_pvars_no_addr_taken = get_sorted_cand (Vset.diff dead_pvars addr_taken_vars) in
let dead_pvars_to_add =
if exit_node_is_succ n (* add dead address taken vars just before the exit node *)
then dead_pvars_no_addr_taken @ (get_sorted_cand (Vset.inter cand addr_taken_vars))
else dead_pvars_no_addr_taken in
incr_dead_pvars_added dead_pvars_to_add;
if !dead_pvars_added < dead_pvars_limit
then add_dead_pvars_after_conditionals_join cfg n dead_pvars_to_add);
IList.iter
(fun n -> (* generate nullify instructions *)
match Cfg.Node.get_kind n with
| Cfg.Node.Start_node _ ->
(* avoid nullifying declared locals/params *)
()
| _ ->
let dead_pvs_after = Cfg.Node.get_dead_pvars n true in
let dead_pvs_before = Cfg.Node.get_dead_pvars n false in
node_add_nullify_instrs n dead_pvs_after dead_pvs_before)
(Cfg.Procdesc.get_nodes pdesc);
Table.reset ()
(** mutate the cfg/cg to add dynamic dispatch handling *)
let add_dispatch_calls cfg cg tenv f_translate_typ_opt =
let pname_translate_types pname =
match f_translate_typ_opt with
| Some f_translate_typ ->
(match pname with
| Procname.Java pname_java ->
let param_type_strs =
IList.map Procname.java_type_to_string (Procname.java_get_parameters pname_java) in
let receiver_type_str = Procname.java_get_class_name pname_java in
let return_type_str = Procname.java_get_return_type pname_java in
IList.iter
(fun typ_str -> f_translate_typ tenv typ_str)
(return_type_str :: (receiver_type_str :: param_type_strs))
| Procname.C _
| Procname.ObjC_Cpp _
| Procname.Block _ ->
(* TODO: support this for C/CPP/Obj-C *)
())
| None -> () in
let node_add_dispatch_calls caller_pname node =
(* TODO: handle dynamic dispatch for virtual calls as well *)
let call_flags_is_dispatch call_flags =
(* if sound dispatch is turned off, only consider dispatch for interface calls *)
(Config.sound_dynamic_dispatch && call_flags.Sil.cf_virtual) ||
call_flags.Sil.cf_interface in
let instr_is_dispatch_call = function
| Sil.Call (_, _, _, _, call_flags) -> call_flags_is_dispatch call_flags
| _ -> false in
let has_dispatch_call instrs =
IList.exists instr_is_dispatch_call instrs in
let replace_dispatch_calls = function
| Sil.Call (ret_ids, (Sil.Const (Sil.Cfun callee_pname) as call_exp),
(((_, receiver_typ) :: _) as args), loc, call_flags) as instr
when call_flags_is_dispatch call_flags ->
(* the frontend should not populate the list of targets *)
assert (call_flags.Sil.cf_targets = []);
let receiver_typ_no_ptr = match receiver_typ with
| Sil.Tptr (typ', _) ->
typ'
| _ ->
receiver_typ in
let sorted_overrides =
let overrides = Prover.get_overrides_of tenv receiver_typ_no_ptr callee_pname in
IList.sort (fun (_, p1) (_, p2) -> Procname.compare p1 p2) overrides in
(match sorted_overrides with
| ((_, target_pname) :: _) as all_targets ->
let targets_to_add =
if Config.sound_dynamic_dispatch then
IList.map snd all_targets
else
(* if sound dispatch is turned off, consider only the first target. we do this
because choosing all targets is too expensive for everyday use *)
[target_pname] in
IList.iter
(fun target_pname ->
pname_translate_types target_pname;
Cg.add_edge cg caller_pname target_pname)
targets_to_add;
let call_flags' = { call_flags with Sil.cf_targets = targets_to_add; } in
Sil.Call (ret_ids, call_exp, args, loc, call_flags')
| [] -> instr)
| instr -> instr in
let instrs = Cfg.Node.get_instrs node in
if has_dispatch_call instrs then
IList.map replace_dispatch_calls instrs
|> Cfg.Node.replace_instrs node in
let proc_add_dispach_calls pname pdesc =
Cfg.Procdesc.iter_nodes (node_add_dispatch_calls pname) pdesc in
Cfg.iter_proc_desc cfg proc_add_dispach_calls
(** add instructions to perform abstraction *)
let add_abstraction_instructions cfg =
let open Cfg in
(* true if there is a succ node s.t.: it is an exit node, or the succ of >1 nodes *)
let converging_node node =
let is_exit node = match Node.get_kind node with
| Node.Exit_node _ -> true
| _ -> false in
let succ_nodes = Node.get_succs node in
if IList.exists is_exit succ_nodes then true
else match succ_nodes with
| [] -> false
| [h] -> IList.length (Node.get_preds h) > 1
| _ -> false in
let node_requires_abstraction node =
match Node.get_kind node with
| Node.Start_node _
| Node.Join_node ->
false
| Node.Exit_node _
| Node.Stmt_node _
| Node.Prune_node _
| Node.Skip_node _ ->
converging_node node in
let all_nodes = Node.get_all_nodes cfg in
let do_node node =
let loc = Node.get_last_loc node in
if node_requires_abstraction node then Node.append_instrs_temps node [Sil.Abstract loc] [] in
IList.iter do_node all_nodes
(** add instructions to remove temporaries *)
let add_removetemps_instructions cfg =
let open Cfg in
let all_nodes = Node.get_all_nodes cfg in
let do_node node =
let loc = Node.get_last_loc node in
let temps = Node.get_temps node in
if temps != [] then Node.append_instrs_temps node [Sil.Remove_temps (temps, loc)] [] in
IList.iter do_node all_nodes
module BackwardCfg = ProcCfg.Backward(ProcCfg.Exceptional)
module LivenessAnalysis =
AbstractInterpreter.Make
(BackwardCfg)
(Scheduler.ReversePostorder)
(Liveness.Domain)
(Liveness.TransferFunctions)
module VarDomain = AbstractDomain.FiniteSet(Var.Set)
(* (reaching non-nullified vars) * (vars to nullify) *)
module NullifyDomain = AbstractDomain.Pair (VarDomain) (VarDomain)
(** computes the non-nullified reaching definitions at the end of each node by building on the
results of a liveness analysis to be precise, what we want to compute is:
to_nullify := (live_before U non_nullifed_reaching_defs) - live_after
non_nullified_reaching_defs := non_nullified_reaching_defs - to_nullify
Note that this can't be done with by combining the results of reaching definitions and liveness
after the fact, nor can it be done with liveness alone. We will insert nullify instructions for
each pvar in to_nullify afer we finish the analysis. Nullify instructions speed up the analysis
by enabling it to GC state that will no longer be read. *)
module NullifyTransferFunctions = struct
type astate = NullifyDomain.astate
type extras = LivenessAnalysis.inv_map
type node_id = BackwardCfg.node_id
let postprocess ((reaching_defs, _) as astate) node_id { ProcData.extras; } =
match LivenessAnalysis.extract_state node_id extras with
(* note: because the analysis backward, post and pre are reversed *)
| Some { LivenessAnalysis.post = live_before; pre = live_after; } ->
let to_nullify = VarDomain.diff (VarDomain.union live_before reaching_defs) live_after in
let reaching_defs' = VarDomain.diff reaching_defs to_nullify in
(reaching_defs', to_nullify)
| None -> astate
let exec_instr ((active_defs, to_nullify) as astate) _ = function
| Sil.Set (Sil.Lvar lhs_pvar, _, _, _) ->
VarDomain.add (Var.ProgramVar lhs_pvar) active_defs, to_nullify
| Sil.Set _ | Letderef _ | Call _ | Prune _ | Declare_locals _ | Stackop _ | Remove_temps _
| Abstract _ ->
astate
| Sil.Nullify _ ->
failwith "Should not add nullify instructions before running nullify analysis!"
end
module NullifyAnalysis =
AbstractInterpreter.Make
(ProcCfg.Exceptional)
(Scheduler.ReversePostorder)
(NullifyDomain)
(NullifyTransferFunctions)
let add_nullify_instrs tenv _ pdesc =
let liveness_proc_cfg = BackwardCfg.from_pdesc pdesc in
let proc_data_no_extras = ProcData.make_default pdesc tenv in
let liveness_inv_map = LivenessAnalysis.exec_cfg liveness_proc_cfg proc_data_no_extras in
let address_taken_vars =
if Procname.is_java (Cfg.Procdesc.get_proc_name pdesc)
then AddressTaken.Domain.empty (* can't take the address of a variable in Java *)
else
match AddressTaken.Analyzer.compute_post proc_data_no_extras with
| Some post -> post
| None -> AddressTaken.Domain.empty in
let nullify_proc_cfg = ProcCfg.Exceptional.from_pdesc pdesc in
let nullify_proc_data = ProcData.make pdesc tenv liveness_inv_map in
let nullify_inv_map = NullifyAnalysis.exec_cfg nullify_proc_cfg nullify_proc_data in
(* only nullify pvars that are local; don't nullify those that can escape *)
let is_local pvar =
not (Pvar.is_return pvar || Pvar.is_global pvar) in
let node_add_nullify_instructions node pvars =
let loc = Cfg.Node.get_last_loc node in
let nullify_instrs =
IList.filter is_local pvars
|> IList.map (fun pvar -> Sil.Nullify (pvar, loc, false)) in
if nullify_instrs <> []
then Cfg.Node.append_instrs_temps node (IList.rev nullify_instrs) [] in
IList.iter
(fun node ->
match NullifyAnalysis.extract_post (ProcCfg.Exceptional.id node) nullify_inv_map with
| Some (_, to_nullify) ->
let pvars_to_nullify =
Var.Set.fold
(fun var acc -> match var with
(* we nullify all address taken variables at the end of the procedure *)
| ProgramVar pvar when not (AddressTaken.Domain.mem pvar address_taken_vars) ->
pvar :: acc
| _ -> acc)
to_nullify
[] in
node_add_nullify_instructions node pvars_to_nullify
| None -> ())
(ProcCfg.Exceptional.nodes nullify_proc_cfg);
(* nullify all address taken variables *)
if not (AddressTaken.Domain.is_empty address_taken_vars)
then
let exit_node = ProcCfg.Exceptional.exit_node nullify_proc_cfg in
node_add_nullify_instructions exit_node (AddressTaken.Domain.elements address_taken_vars)
let old_nullify_analysis = false
let doit ?(f_translate_typ=None) cfg cg tenv =
add_removetemps_instructions cfg;
if old_nullify_analysis
then
begin
AllPreds.mk_table cfg;
Cfg.iter_proc_desc cfg (analyze_and_annotate_proc cfg tenv);
AllPreds.clear_table ()
end
else
Cfg.iter_proc_desc cfg (add_nullify_instrs tenv);
if !Config.curr_language = Config.Java
then add_dispatch_calls cfg cg tenv f_translate_typ;
add_abstraction_instructions cfg;