[thread-safety] Interprocedural escape analysis for formals/locals (essentially step (3) in Sam's list).

Reviewed By: sblackshear

Differential Revision: D5293383

fbshipit-source-id: 96c332d
master
Kyriakos Nikolaos Gkorogiannis 7 years ago committed by Facebook Github Bot
parent d4c728913a
commit 410728d18f

@ -451,7 +451,7 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
| _ | _
-> false -> false
let make_container_write callee_pname receiver_ap callee_loc tenv = let make_container_write callee_pname actuals receiver_ap callee_loc tenv =
(* create a dummy write that represents mutating the contents of the container *) (* create a dummy write that represents mutating the contents of the container *)
let open Domain in let open Domain in
let callee_accesses = let callee_accesses =
@ -467,7 +467,11 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
AccessDomain.add_access (Unprotected (Some 0)) AccessDomain.add_access (Unprotected (Some 0))
(make_access dummy_access_ap Write callee_loc) AccessDomain.empty (make_access dummy_access_ap Write callee_loc) AccessDomain.empty
in in
Some (true, false, false, callee_accesses, AttributeSetDomain.empty) (* TODO: for now all formals escape *)
(* we need a more intelligent escape analysis, that branches on whether
we own the container *)
let escapee_formals = List.length actuals |> List.range 0 |> FormalsDomain.of_list in
Some (true, false, false, callee_accesses, AttributeSetDomain.empty, escapee_formals)
let get_summary caller_pdesc callee_pname actuals callee_loc tenv = let get_summary caller_pdesc callee_pname actuals callee_loc tenv =
if is_container_write callee_pname tenv then if is_container_write callee_pname tenv then
@ -479,7 +483,7 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
-> failwithf "Call to %a is marked as a container write, but has no receiver" -> failwithf "Call to %a is marked as a container write, but has no receiver"
Typ.Procname.pp callee_pname Typ.Procname.pp callee_pname
in in
make_container_write callee_pname receiver_ap callee_loc tenv make_container_write callee_pname actuals receiver_ap callee_loc tenv
else Summary.read_summary caller_pdesc callee_pname else Summary.read_summary caller_pdesc callee_pname
(* return true if the given procname boxes a primitive type into a reference type *) (* return true if the given procname boxes a primitive type into a reference type *)
@ -502,6 +506,19 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
~f:(fun acc exp -> add_access exp loc Read acc locks threads attribute_map proc_data) ~f:(fun acc exp -> add_access exp loc Read acc locks threads attribute_map proc_data)
exps ~init:accesses exps ~init:accesses
let add_escapees_from_exp rhs_exp extras escapees =
HilExp.get_access_paths rhs_exp |> List.rev_map ~f:(Domain.Escapee.of_access_path extras)
|> Domain.EscapeeDomain.add_from_list escapees
let add_escapees_from_exp_list exp_list extras escapees =
List.fold ~init:escapees exp_list ~f:(fun acc exp -> add_escapees_from_exp exp extras acc)
let add_escapees_from_call actuals escapee_formals extras escapees =
let escapee_actuals =
Domain.FormalsDomain.elements escapee_formals |> List.rev_map ~f:(List.nth_exn actuals)
in
add_escapees_from_exp_list escapee_actuals extras escapees
let exec_instr (astate: Domain.astate) ({ProcData.extras; tenv; pdesc} as proc_data) _ let exec_instr (astate: Domain.astate) ({ProcData.extras; tenv; pdesc} as proc_data) _
(instr: HilInstr.t) = (instr: HilInstr.t) =
let open Domain in let open Domain in
@ -515,7 +532,9 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
AttributeMapDomain.add_attribute (ret_base, []) Attribute.unconditionally_owned AttributeMapDomain.add_attribute (ret_base, []) Attribute.unconditionally_owned
astate.attribute_map astate.attribute_map
in in
{astate with accesses; attribute_map} (* TODO: we need to model escaping formals, now just assume all escape *)
let escapees = add_escapees_from_exp_list actuals extras astate.escapees in
{astate with accesses; attribute_map; escapees}
| Call (ret_opt, Direct callee_pname, actuals, call_flags, loc) | Call (ret_opt, Direct callee_pname, actuals, call_flags, loc)
-> ( -> (
let accesses = let accesses =
@ -568,7 +587,8 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
, callee_threads , callee_threads
, callee_locks , callee_locks
, callee_accesses , callee_accesses
, return_attributes ) , return_attributes
, escapee_formals )
-> let update_caller_accesses pre callee_accesses caller_accesses = -> let update_caller_accesses pre callee_accesses caller_accesses =
let combined_accesses = let combined_accesses =
PathDomain.with_callsite callee_accesses (CallSite.make callee_pname loc) PathDomain.with_callsite callee_accesses (CallSite.make callee_pname loc)
@ -645,9 +665,15 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
propagate_return_attributes ret_opt return_attributes actuals propagate_return_attributes ret_opt return_attributes actuals
astate.attribute_map extras astate.attribute_map extras
in in
{thumbs_up; locks; threads; accesses; attribute_map} let escapees =
add_escapees_from_call actuals escapee_formals extras astate.escapees
in
{thumbs_up; locks; threads; accesses; attribute_map; escapees}
| None | None
-> let should_assume_returns_ownership (call_flags: CallFlags.t) actuals = -> (* TODO: assume actuals escape, should we? *)
let new_escapees = add_escapees_from_exp_list actuals extras astate.escapees in
let astate = {astate with escapees= new_escapees} in
let should_assume_returns_ownership (call_flags: CallFlags.t) actuals =
(* assume non-interface methods with no summary and no parameters return (* assume non-interface methods with no summary and no parameters return
ownership *) ownership *)
not call_flags.cf_interface && List.is_empty actuals not call_flags.cf_interface && List.is_empty actuals
@ -721,7 +747,12 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
let attribute_map = let attribute_map =
propagate_attributes lhs_access_path rhs_exp astate.attribute_map extras propagate_attributes lhs_access_path rhs_exp astate.attribute_map extras
in in
{astate with accesses; attribute_map} (* assigning to the return variable leads to no new escapees *)
let escapees =
if fst lhs_access_path |> fst |> Var.is_return then astate.escapees
else add_escapees_from_exp rhs_exp extras astate.escapees
in
{astate with accesses; attribute_map; escapees}
| Assume (assume_exp, _, _, loc) | Assume (assume_exp, _, _, loc)
-> let rec eval_binop op var e1 e2 = -> let rec eval_binop op var e1 e2 =
match (eval_bexp var e1, eval_bexp var e2) with match (eval_bexp var e1, eval_bexp var e2) with
@ -877,12 +908,14 @@ let empty_post =
let initial_thumbs_up = true let initial_thumbs_up = true
and initial_known_on_ui_thread = false and initial_known_on_ui_thread = false
and has_lock = false and has_lock = false
and return_attrs = ThreadSafetyDomain.AttributeSetDomain.empty in and return_attrs = ThreadSafetyDomain.AttributeSetDomain.empty
and escapee_formals = ThreadSafetyDomain.FormalsDomain.empty in
( initial_thumbs_up ( initial_thumbs_up
, initial_known_on_ui_thread , initial_known_on_ui_thread
, has_lock , has_lock
, ThreadSafetyDomain.AccessDomain.empty , ThreadSafetyDomain.AccessDomain.empty
, return_attrs ) , return_attrs
, escapee_formals )
let analyze_procedure {Callbacks.proc_desc; tenv; summary} = let analyze_procedure {Callbacks.proc_desc; tenv; summary} =
let is_initializer tenv proc_name = let is_initializer tenv proc_name =
@ -920,7 +953,7 @@ let analyze_procedure {Callbacks.proc_desc; tenv; summary} =
else ({ThreadSafetyDomain.empty with threads}, IdAccessPathMapDomain.empty) else ({ThreadSafetyDomain.empty with threads}, IdAccessPathMapDomain.empty)
in in
match Analyzer.compute_post proc_data ~initial ~debug:false with match Analyzer.compute_post proc_data ~initial ~debug:false with
| Some ({thumbs_up; threads; locks; accesses; attribute_map}, _) | Some ({thumbs_up; threads; locks; accesses; attribute_map; escapees}, _)
-> let return_var_ap = -> let return_var_ap =
AccessPath.of_pvar AccessPath.of_pvar
(Pvar.get_ret_pvar (Procdesc.get_proc_name proc_desc)) (Pvar.get_ret_pvar (Procdesc.get_proc_name proc_desc))
@ -948,20 +981,21 @@ let analyze_procedure {Callbacks.proc_desc; tenv; summary} =
| _ | _
-> locks -> locks
in in
let post = (thumbs_up, threads, update_locks, accesses, return_attributes) in let escapee_formals = FormalsDomain.of_escapees escapees in
let post =
(thumbs_up, threads, update_locks, accesses, return_attributes, escapee_formals)
in
Summary.update_summary post summary Summary.update_summary post summary
| None | None
-> summary ) -> summary )
else Summary.update_summary empty_post summary else Summary.update_summary empty_post summary
(* we assume two access paths can alias if their access parts are equal (we ignore the base). *)
let can_alias access_path1 access_path2 =
List.compare AccessPath.compare_access (snd access_path1) (snd access_path2)
module AccessListMap = Caml.Map.Make (struct module AccessListMap = Caml.Map.Make (struct
type t = AccessPath.Raw.t [@@deriving compare] type t = AccessPath.Raw.t
let compare = can_alias (* TODO -- keep this compare to satisfy the order of tests, consider using Raw.compare *)
let compare access_path1 access_path2 =
List.compare AccessPath.compare_access (snd access_path1) (snd access_path2)
end) end)
let get_current_class_and_threadsafe_superclasses tenv pname = let get_current_class_and_threadsafe_superclasses tenv pname =
@ -1032,7 +1066,7 @@ let trace_of_pname orig_sink orig_pdesc callee_pname =
let open ThreadSafetyDomain in let open ThreadSafetyDomain in
let orig_access = PathDomain.Sink.kind orig_sink in let orig_access = PathDomain.Sink.kind orig_sink in
match Summary.read_summary orig_pdesc callee_pname with match Summary.read_summary orig_pdesc callee_pname with
| Some (_, _, _, access_map, _) | Some (_, _, _, access_map, _, _)
-> get_all_accesses -> get_all_accesses
(fun access -> Int.equal (Access.compare (PathDomain.Sink.kind access) orig_access) 0) (fun access -> Int.equal (Access.compare (PathDomain.Sink.kind access) orig_access) 0)
access_map access_map
@ -1397,7 +1431,7 @@ let should_filter_access (_, path) =
now, our abstraction is an access path like x.f.g whose concretization is the set of memory cells now, our abstraction is an access path like x.f.g whose concretization is the set of memory cells
that x.f.g may point to during execution *) that x.f.g may point to during execution *)
let make_results_table file_env = let make_results_table file_env =
let aggregate_post (_, threaded, _, accesses, _) tenv pdesc acc = let aggregate_post (_, threaded, _, accesses, _, _) tenv pdesc acc =
let open ThreadSafetyDomain in let open ThreadSafetyDomain in
AccessDomain.fold AccessDomain.fold
(fun pre accesses acc -> (fun pre accesses acc ->

@ -178,12 +178,47 @@ module AccessDomain = struct
with Not_found -> PathDomain.empty with Not_found -> PathDomain.empty
end end
module Escapee = struct
type t = Formal of int | Local of Var.t [@@deriving compare]
let pp fmt = function
| Formal fml
-> F.fprintf fmt "Formal(%d)" fml
| Local loc
-> F.fprintf fmt "Local(%a)" Var.pp loc
let of_access_path extras (base, _) =
match FormalMap.get_formal_index base extras with
| Some fml
-> Formal fml
| None
-> Local (fst base)
end
module EscapeeDomain = struct
include AbstractDomain.FiniteSet (Escapee)
let add_from_list escapees escapee_list =
List.fold ~init:escapees escapee_list ~f:(fun acc v -> add v acc)
end
module FormalsDomain = struct
include AbstractDomain.FiniteSet (Int)
let of_escapees escapees =
let aux escapee acc =
match escapee with Escapee.Formal fml -> add fml acc | Escapee.Local _ -> acc
in
EscapeeDomain.fold aux escapees empty
end
type astate = type astate =
{ thumbs_up: ThumbsUpDomain.astate { thumbs_up: ThumbsUpDomain.astate
; threads: ThreadsDomain.astate ; threads: ThreadsDomain.astate
; locks: LocksDomain.astate ; locks: LocksDomain.astate
; accesses: AccessDomain.astate ; accesses: AccessDomain.astate
; attribute_map: AttributeMapDomain.astate } ; attribute_map: AttributeMapDomain.astate
; escapees: EscapeeDomain.astate }
type summary = type summary =
ThumbsUpDomain.astate ThumbsUpDomain.astate
@ -191,6 +226,7 @@ type summary =
* LocksDomain.astate * LocksDomain.astate
* AccessDomain.astate * AccessDomain.astate
* AttributeSetDomain.astate * AttributeSetDomain.astate
* FormalsDomain.astate
let empty = let empty =
let thumbs_up = true in let thumbs_up = true in
@ -198,7 +234,8 @@ let empty =
let locks = false in let locks = false in
let accesses = AccessDomain.empty in let accesses = AccessDomain.empty in
let attribute_map = AccessPath.RawMap.empty in let attribute_map = AccessPath.RawMap.empty in
{thumbs_up; threads; locks; accesses; attribute_map} let escapees = EscapeeDomain.empty in
{thumbs_up; threads; locks; accesses; attribute_map; escapees}
let ( <= ) ~lhs ~rhs = let ( <= ) ~lhs ~rhs =
if phys_equal lhs rhs then true if phys_equal lhs rhs then true
@ -206,6 +243,7 @@ let ( <= ) ~lhs ~rhs =
&& LocksDomain.( <= ) ~lhs:lhs.locks ~rhs:rhs.locks && LocksDomain.( <= ) ~lhs:lhs.locks ~rhs:rhs.locks
&& AccessDomain.( <= ) ~lhs:lhs.accesses ~rhs:rhs.accesses && AccessDomain.( <= ) ~lhs:lhs.accesses ~rhs:rhs.accesses
&& AttributeMapDomain.( <= ) ~lhs:lhs.attribute_map ~rhs:rhs.attribute_map && AttributeMapDomain.( <= ) ~lhs:lhs.attribute_map ~rhs:rhs.attribute_map
&& EscapeeDomain.( <= ) ~lhs:lhs.escapees ~rhs:rhs.escapees
let join astate1 astate2 = let join astate1 astate2 =
if phys_equal astate1 astate2 then astate1 if phys_equal astate1 astate2 then astate1
@ -215,7 +253,8 @@ let join astate1 astate2 =
let locks = LocksDomain.join astate1.locks astate2.locks in let locks = LocksDomain.join astate1.locks astate2.locks in
let accesses = AccessDomain.join astate1.accesses astate2.accesses in let accesses = AccessDomain.join astate1.accesses astate2.accesses in
let attribute_map = AttributeMapDomain.join astate1.attribute_map astate2.attribute_map in let attribute_map = AttributeMapDomain.join astate1.attribute_map astate2.attribute_map in
{thumbs_up; threads; locks; accesses; attribute_map} let escapees = EscapeeDomain.join astate1.escapees astate2.escapees in
{thumbs_up; threads; locks; accesses; attribute_map; escapees}
let widen ~prev ~next ~num_iters = let widen ~prev ~next ~num_iters =
if phys_equal prev next then prev if phys_equal prev next then prev
@ -227,16 +266,17 @@ let widen ~prev ~next ~num_iters =
let attribute_map = let attribute_map =
AttributeMapDomain.widen ~prev:prev.attribute_map ~next:next.attribute_map ~num_iters AttributeMapDomain.widen ~prev:prev.attribute_map ~next:next.attribute_map ~num_iters
in in
{thumbs_up; threads; locks; accesses; attribute_map} let escapees = EscapeeDomain.widen ~prev:prev.escapees ~next:next.escapees ~num_iters in
{thumbs_up; threads; locks; accesses; attribute_map; escapees}
let pp_summary fmt (thumbs_up, threads, locks, accesses, return_attributes) = let pp_summary fmt (thumbs_up, threads, locks, accesses, return_attributes, escapee_formals) =
F.fprintf fmt F.fprintf fmt
"@\nThumbsUp: %a, Threads: %a, Locks: %a @\nAccesses %a @\nReturn Attributes: %a@\n" "@\nThumbsUp: %a, Threads: %a, Locks: %a @\nAccesses %a @\nReturn Attributes: %a @\nEscapee Formals: %a @\n"
ThumbsUpDomain.pp thumbs_up ThreadsDomain.pp threads LocksDomain.pp locks AccessDomain.pp ThumbsUpDomain.pp thumbs_up ThreadsDomain.pp threads LocksDomain.pp locks AccessDomain.pp
accesses AttributeSetDomain.pp return_attributes accesses AttributeSetDomain.pp return_attributes FormalsDomain.pp escapee_formals
let pp fmt {thumbs_up; threads; locks; accesses; attribute_map} = let pp fmt {thumbs_up; threads; locks; accesses; attribute_map; escapees} =
F.fprintf fmt F.fprintf fmt
"@\nThumbsUp: %a, Threads: %a, Locks: %a @\nAccesses %a @\nReturn Attributes: %a@\n" "@\nThumbsUp: %a, Threads: %a, Locks: %a @\nAccesses %a @\nReturn Attributes: %a @\nEscapees: %a @\n"
ThumbsUpDomain.pp thumbs_up ThreadsDomain.pp threads LocksDomain.pp locks AccessDomain.pp ThumbsUpDomain.pp thumbs_up ThreadsDomain.pp threads LocksDomain.pp locks AccessDomain.pp
accesses AttributeMapDomain.pp attribute_map accesses AttributeMapDomain.pp attribute_map EscapeeDomain.pp escapees

@ -118,13 +118,34 @@ end
module AccessDomain : sig module AccessDomain : sig
include module type of AbstractDomain.Map (AccessPrecondition) (PathDomain) include module type of AbstractDomain.Map (AccessPrecondition) (PathDomain)
(* add the given (access, precondition) pair to the map *)
val add_access : AccessPrecondition.t -> TraceElem.t -> astate -> astate val add_access : AccessPrecondition.t -> TraceElem.t -> astate -> astate
(** add the given (access, precondition) pair to the map *)
(* get all accesses with the given precondition *)
val get_accesses : AccessPrecondition.t -> astate -> PathDomain.astate val get_accesses : AccessPrecondition.t -> astate -> PathDomain.astate
(** get all accesses with the given precondition *)
end
(** a formal or a local variable that may escape *)
module Escapee : sig
type t = Formal of int | Local of Var.t [@@deriving compare]
val pp : F.formatter -> t -> unit
val of_access_path : FormalMap.t -> AccessPath.Raw.t -> t
end
(** set of formals or locals that may escape *)
module EscapeeDomain : sig
include module type of AbstractDomain.FiniteSet (Escapee)
val add_from_list : astate -> Escapee.t list -> astate
end
(** Domain that only includes escaping formals, for use in summary *)
module FormalsDomain : sig
include module type of AbstractDomain.FiniteSet (Int)
val of_escapees : EscapeeDomain.astate -> astate
end end
type astate = type astate =
@ -134,16 +155,19 @@ type astate =
; accesses: AccessDomain.astate ; accesses: AccessDomain.astate
(** read and writes accesses performed without ownership permissions *) (** read and writes accesses performed without ownership permissions *)
; attribute_map: AttributeMapDomain.astate ; attribute_map: AttributeMapDomain.astate
(** map of access paths to attributes such as owned, functional, ... *) } (** map of access paths to attributes such as owned, functional, ... *)
; escapees: EscapeeDomain.astate (** set of formals and locals that may escape *) }
(** same as astate, but without [id_map]/[owned] (since they are local) and with the addition of the (** same as astate, but without [attribute_map] (since these involve locals)
attributes associated with the return value *) and with the addition of the attributes associated with the return value
as well as the set of formals which may escape *)
type summary = type summary =
ThumbsUpDomain.astate ThumbsUpDomain.astate
* ThreadsDomain.astate * ThreadsDomain.astate
* LocksDomain.astate * LocksDomain.astate
* AccessDomain.astate * AccessDomain.astate
* AttributeSetDomain.astate * AttributeSetDomain.astate
* FormalsDomain.astate
include AbstractDomain.WithBottom with type astate := astate include AbstractDomain.WithBottom with type astate := astate

Loading…
Cancel
Save