[thread-safety] add map for conditional accesses

Summary:
If an access path rooted in some parameter `p` is accessed outside of synchronizaton, but `p` is owned by the caller, then we should not warn.
We will implement this by separating writes into "conditional" (safe if a certain parameter is owned by the caller" and "unconditional" (safe only if the caller synchronizes appropriately).
This diff just introduces the map type for conditional writes and changes the transfer functions accordingly.
We'll actually use the map in a follow-up.

Reviewed By: peterogithub

Differential Revision: D4400987

fbshipit-source-id: d2b8af8
master
Sam Blackshear 8 years ago committed by Facebook Github Bot
parent 72e17403fa
commit f065f7653a

@ -100,10 +100,7 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
| Some rhs_access_path -> IdAccessPathMapDomain.add lhs_id rhs_access_path id_map | Some rhs_access_path -> IdAccessPathMapDomain.add lhs_id rhs_access_path id_map
| None -> id_map | None -> id_map
let exec_instr let exec_instr (astate : Domain.astate) { ProcData.pdesc; tenv; } _ =
({ ThreadSafetyDomain.locks; reads; writes; id_map; owned; } as astate)
{ ProcData.pdesc; tenv; } _ =
let is_allocation pn = let is_allocation pn =
Procname.equal pn BuiltinDecl.__new || Procname.equal pn BuiltinDecl.__new ||
Procname.equal pn BuiltinDecl.__new_array in Procname.equal pn BuiltinDecl.__new_array in
@ -124,20 +121,21 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
let dummy_fieldname = let dummy_fieldname =
Ident.create_fieldname (Mangled.from_string (Procname.get_method pn)) 0 in Ident.create_fieldname (Mangled.from_string (Procname.get_method pn)) 0 in
let dummy_access_exp = Exp.Lfield (exp, dummy_fieldname, typ) in let dummy_access_exp = Exp.Lfield (exp, dummy_fieldname, typ) in
let writes = let unconditional_writes =
add_path_to_state dummy_access_exp typ loc astate.writes astate.id_map astate.owned tenv in add_path_to_state
{ astate with writes; } in dummy_access_exp typ loc astate.unconditional_writes astate.id_map astate.owned tenv in
{ astate with unconditional_writes; } in
let is_unprotected is_locked = let is_unprotected is_locked =
not is_locked && not (Procdesc.is_java_synchronized pdesc) in not is_locked && not (Procdesc.is_java_synchronized pdesc) in
let f_resolve_id = resolve_id id_map in let f_resolve_id = resolve_id astate.id_map in
function function
| Sil.Call (Some (lhs_id, lhs_typ), Const (Cfun pn), _, _, _) when is_allocation pn -> | Sil.Call (Some (lhs_id, lhs_typ), Const (Cfun pn), _, _, _) when is_allocation pn ->
begin begin
match AccessPath.of_lhs_exp (Exp.Var lhs_id) lhs_typ ~f_resolve_id with match AccessPath.of_lhs_exp (Exp.Var lhs_id) lhs_typ ~f_resolve_id with
| Some lhs_access_path -> | Some lhs_access_path ->
let owned' = ThreadSafetyDomain.OwnershipDomain.add lhs_access_path owned in let owned = ThreadSafetyDomain.OwnershipDomain.add lhs_access_path astate.owned in
{ astate with owned = owned'; } { astate with owned; }
| None -> | None ->
astate astate
end end
@ -150,7 +148,7 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
| Unlock -> | Unlock ->
{ astate with locks = false; } { astate with locks = false; }
| NoEffect -> | NoEffect ->
if is_unprotected locks && is_container_write pn tenv if is_unprotected astate.locks && is_container_write pn tenv
then then
match actuals with match actuals with
| (receiver_exp, receiver_typ) :: _ -> | (receiver_exp, receiver_typ) :: _ ->
@ -162,21 +160,23 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
else else
begin begin
match Summary.read_summary pdesc pn with match Summary.read_summary pdesc pn with
| Some (callee_locks, callee_reads, callee_writes) -> | Some (callee_locks, callee_reads, _, callee_unconditional_writes) ->
let locks' = callee_locks || locks in let locks' = callee_locks || astate.locks in
let astate' = let astate' =
(* TODO (14842325): report on constructors that aren't threadsafe (* TODO (14842325): report on constructors that aren't threadsafe
(e.g., constructors that access static fields) *) (e.g., constructors that access static fields) *)
if is_unprotected locks' if is_unprotected locks'
then then
let call_site = CallSite.make pn loc in let call_site = CallSite.make pn loc in
let reads' = let reads =
ThreadSafetyDomain.PathDomain.with_callsite callee_reads call_site ThreadSafetyDomain.PathDomain.with_callsite callee_reads call_site
|> ThreadSafetyDomain.PathDomain.join reads in |> ThreadSafetyDomain.PathDomain.join astate.reads in
let writes' = let unconditional_writes =
ThreadSafetyDomain.PathDomain.with_callsite callee_writes call_site ThreadSafetyDomain.PathDomain.with_callsite
|> ThreadSafetyDomain.PathDomain.join writes in callee_unconditional_writes
{ astate with reads = reads'; writes = writes'; } call_site
|> ThreadSafetyDomain.PathDomain.join astate.unconditional_writes in
{ astate with reads; unconditional_writes; }
else else
astate in astate in
{ astate' with locks = locks'; } { astate' with locks = locks'; }
@ -190,50 +190,54 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
{ astate with id_map = id_map'; } { astate with id_map = id_map'; }
| Sil.Store (lhs_exp, lhs_typ, rhs_exp, loc) -> | Sil.Store (lhs_exp, lhs_typ, rhs_exp, loc) ->
let writes' = let unconditional_writes =
match lhs_exp with match lhs_exp with
| Lfield ( _, _, typ) when is_unprotected locks -> (* abstracts no lock being held *) | Lfield ( _, _, typ)
add_path_to_state lhs_exp typ loc writes id_map owned tenv when is_unprotected astate.locks -> (* abstracts no lock being held *)
| _ -> writes in add_path_to_state
lhs_exp typ loc astate.unconditional_writes astate.id_map astate.owned tenv
| _ ->
astate.unconditional_writes in
(* if rhs is owned, propagate ownership to lhs. otherwise, remove lhs from ownerhsip set (* if rhs is owned, propagate ownership to lhs. otherwise, remove lhs from ownerhsip set
(since it may have previously held an owned memory loc and is now being reassigned *) (since it may have previously held an owned memory loc and is now being reassigned *)
let owned' = let owned =
match AccessPath.of_lhs_exp lhs_exp lhs_typ ~f_resolve_id, match AccessPath.of_lhs_exp lhs_exp lhs_typ ~f_resolve_id,
AccessPath.of_lhs_exp rhs_exp lhs_typ ~f_resolve_id with AccessPath.of_lhs_exp rhs_exp lhs_typ ~f_resolve_id with
| Some lhs_access_path, Some rhs_access_path -> | Some lhs_access_path, Some rhs_access_path ->
if ThreadSafetyDomain.OwnershipDomain.mem rhs_access_path owned if ThreadSafetyDomain.OwnershipDomain.mem rhs_access_path astate.owned
then ThreadSafetyDomain.OwnershipDomain.add lhs_access_path owned then ThreadSafetyDomain.OwnershipDomain.add lhs_access_path astate.owned
else ThreadSafetyDomain.OwnershipDomain.remove lhs_access_path owned else ThreadSafetyDomain.OwnershipDomain.remove lhs_access_path astate.owned
| Some lhs_access_path, None -> | Some lhs_access_path, None ->
ThreadSafetyDomain.OwnershipDomain.remove lhs_access_path owned ThreadSafetyDomain.OwnershipDomain.remove lhs_access_path astate.owned
| _ -> owned in | _ ->
{ astate with writes = writes'; owned = owned'; } astate.owned in
{ astate with unconditional_writes; owned; }
| Sil.Load (lhs_id, rhs_exp, rhs_typ, loc) -> | Sil.Load (lhs_id, rhs_exp, rhs_typ, loc) ->
let id_map' = analyze_id_assignment (Var.of_id lhs_id) rhs_exp rhs_typ astate in let id_map = analyze_id_assignment (Var.of_id lhs_id) rhs_exp rhs_typ astate in
let reads' = let reads =
match rhs_exp with match rhs_exp with
| Lfield ( _, _, typ) when is_unprotected locks -> | Lfield ( _, _, typ) when is_unprotected astate.locks ->
add_path_to_state rhs_exp typ loc reads id_map owned tenv add_path_to_state rhs_exp typ loc astate.reads astate.id_map astate.owned tenv
| _ -> | _ ->
reads in astate.reads in
(* if rhs is owned, propagate ownership to lhs *) (* if rhs is owned, propagate ownership to lhs *)
let owned' = let owned =
match AccessPath.of_lhs_exp rhs_exp rhs_typ ~f_resolve_id with match AccessPath.of_lhs_exp rhs_exp rhs_typ ~f_resolve_id with
| Some rhs_access_path | Some rhs_access_path
when ThreadSafetyDomain.OwnershipDomain.mem rhs_access_path owned -> when ThreadSafetyDomain.OwnershipDomain.mem rhs_access_path astate.owned ->
ThreadSafetyDomain.OwnershipDomain.add (AccessPath.of_id lhs_id rhs_typ) owned ThreadSafetyDomain.OwnershipDomain.add (AccessPath.of_id lhs_id rhs_typ) astate.owned
| _ -> | _ ->
owned in astate.owned in
{ astate with Domain.reads = reads'; id_map = id_map'; owned = owned'; } { astate with Domain.reads; id_map; owned; }
| Sil.Remove_temps (ids, _) -> | Sil.Remove_temps (ids, _) ->
let id_map' = let id_map =
IList.fold_left IList.fold_left
(fun acc id -> IdAccessPathMapDomain.remove (Var.of_id id) acc) (fun acc id -> IdAccessPathMapDomain.remove (Var.of_id id) acc)
astate.id_map astate.id_map
ids in ids in
{ astate with id_map = id_map'; } { astate with id_map; }
| _ -> | _ ->
astate astate
@ -338,7 +342,11 @@ let make_results_table get_proc_desc file_env =
in in
let compute_post_for_procedure = (* takes proc_env as arg *) let compute_post_for_procedure = (* takes proc_env as arg *)
fun (idenv, tenv, proc_name, proc_desc) -> fun (idenv, tenv, proc_name, proc_desc) ->
let empty = false, ThreadSafetyDomain.PathDomain.empty, ThreadSafetyDomain.PathDomain.empty in let empty =
false,
ThreadSafetyDomain.PathDomain.empty,
ThreadSafetyDomain.ConditionalWritesDomain.empty,
ThreadSafetyDomain.PathDomain.empty in
(* convert the abstract state to a summary by dropping the id map *) (* convert the abstract state to a summary by dropping the id map *)
let compute_post ({ ProcData.pdesc; tenv; } as proc_data) = let compute_post ({ ProcData.pdesc; tenv; } as proc_data) =
if should_analyze_proc pdesc tenv if should_analyze_proc pdesc tenv
@ -346,8 +354,10 @@ let make_results_table get_proc_desc file_env =
begin begin
if not (Procdesc.did_preanalysis pdesc) then Preanal.do_liveness pdesc tenv; if not (Procdesc.did_preanalysis pdesc) then Preanal.do_liveness pdesc tenv;
match Analyzer.compute_post proc_data ~initial:ThreadSafetyDomain.empty with match Analyzer.compute_post proc_data ~initial:ThreadSafetyDomain.empty with
| Some { locks; reads; writes; } -> Some (locks, reads, writes) | Some { locks; reads; conditional_writes; unconditional_writes; } ->
| None -> None Some (locks, reads, conditional_writes, unconditional_writes)
| None ->
None
end end
else else
Some empty in Some empty in
@ -390,7 +400,7 @@ let report_thread_safety_violations ( _, tenv, pname, pdesc) trace =
let open ThreadSafetyDomain in let open ThreadSafetyDomain in
let trace_of_pname callee_pname = let trace_of_pname callee_pname =
match Summary.read_summary pdesc callee_pname with match Summary.read_summary pdesc callee_pname with
| Some (_, _, writes) -> writes | Some (_, _, _, unconditional_writes) -> unconditional_writes
| _ -> PathDomain.empty in | _ -> PathDomain.empty in
let report_one_path ((_, sinks) as path) = let report_one_path ((_, sinks) as path) =
let pp_accesses fmt sink = let pp_accesses fmt sink =
@ -449,8 +459,8 @@ let process_results_table file_env tab =
(should_report_on_all_procs || is_annotated Annotations.ia_is_thread_safe_method pdesc) (should_report_on_all_procs || is_annotated Annotations.ia_is_thread_safe_method pdesc)
&& should_report_on_proc proc_env in && should_report_on_proc proc_env in
ResultsTableType.iter (* report errors for each method *) ResultsTableType.iter (* report errors for each method *)
(fun proc_env (_, _, writes) -> (fun proc_env (_, _, _, unconditional_writes) ->
if should_report proc_env then report_thread_safety_violations proc_env writes) if should_report proc_env then report_thread_safety_violations proc_env unconditional_writes)
tab tab
(*This is a "cluster checker" *) (*This is a "cluster checker" *)

@ -59,13 +59,24 @@ module LocksDomain = AbstractDomain.BooleanAnd
module PathDomain = SinkTrace.Make(TraceElem) module PathDomain = SinkTrace.Make(TraceElem)
module IntMap = PrettyPrintable.MakePPMap(struct
type t = int
let compare = Pervasives.compare
let pp_key fmt = F.fprintf fmt "%d"
end)
module ConditionalWritesDomain = AbstractDomain.Map (IntMap) (PathDomain)
type astate = type astate =
{ {
locks : LocksDomain.astate; locks : LocksDomain.astate;
(** boolean that is true if a lock must currently be held *) (** boolean that is true if a lock must currently be held *)
reads : PathDomain.astate; reads : PathDomain.astate;
(** access paths read outside of synchronization *) (** access paths read outside of synchronization *)
writes : PathDomain.astate; conditional_writes : ConditionalWritesDomain.astate;
(** map of (formal index) -> set of access paths rooted in the formal index that are read
outside of synrchonization if the formal is not owned by the caller *)
unconditional_writes : PathDomain.astate;
(** access paths written outside of synchronization *) (** access paths written outside of synchronization *)
id_map : IdAccessPathMapDomain.astate; id_map : IdAccessPathMapDomain.astate;
(** map used to decompile temporary variables into access paths *) (** map used to decompile temporary variables into access paths *)
@ -73,16 +84,18 @@ type astate =
(** access paths that must be owned by the current function *) (** access paths that must be owned by the current function *)
} }
(** same as astate, but without [id_map] (since it's local) *) (** same as astate, but without [id_map]/[owned] (since they are local) *)
type summary = LocksDomain.astate * PathDomain.astate * PathDomain.astate type summary =
LocksDomain.astate * PathDomain.astate * ConditionalWritesDomain.astate *PathDomain.astate
let empty = let empty =
let locks = false in let locks = false in
let reads = PathDomain.empty in let reads = PathDomain.empty in
let writes = PathDomain.empty in let conditional_writes = ConditionalWritesDomain.empty in
let unconditional_writes = PathDomain.empty in
let id_map = IdAccessPathMapDomain.empty in let id_map = IdAccessPathMapDomain.empty in
let owned = OwnershipDomain.empty in let owned = OwnershipDomain.empty in
{ locks; reads; writes; id_map; owned; } { locks; reads; conditional_writes; unconditional_writes; id_map; owned; }
let (<=) ~lhs ~rhs = let (<=) ~lhs ~rhs =
if phys_equal lhs rhs if phys_equal lhs rhs
@ -90,7 +103,8 @@ let (<=) ~lhs ~rhs =
else else
LocksDomain.(<=) ~lhs:lhs.locks ~rhs:rhs.locks && LocksDomain.(<=) ~lhs:lhs.locks ~rhs:rhs.locks &&
PathDomain.(<=) ~lhs:lhs.reads ~rhs:rhs.reads && PathDomain.(<=) ~lhs:lhs.reads ~rhs:rhs.reads &&
PathDomain.(<=) ~lhs:lhs.writes ~rhs:rhs.writes && ConditionalWritesDomain.(<=) ~lhs:lhs.conditional_writes ~rhs:rhs.conditional_writes &&
PathDomain.(<=) ~lhs:lhs.unconditional_writes ~rhs:rhs.unconditional_writes &&
IdAccessPathMapDomain.(<=) ~lhs:lhs.id_map ~rhs:rhs.id_map && IdAccessPathMapDomain.(<=) ~lhs:lhs.id_map ~rhs:rhs.id_map &&
OwnershipDomain.(<=) ~lhs:lhs.owned ~rhs:rhs.owned OwnershipDomain.(<=) ~lhs:lhs.owned ~rhs:rhs.owned
@ -101,10 +115,13 @@ let join astate1 astate2 =
else else
let locks = LocksDomain.join astate1.locks astate2.locks in let locks = LocksDomain.join astate1.locks astate2.locks in
let reads = PathDomain.join astate1.reads astate2.reads in let reads = PathDomain.join astate1.reads astate2.reads in
let writes = PathDomain.join astate1.writes astate2.writes in let conditional_writes =
ConditionalWritesDomain.join astate1.conditional_writes astate2.conditional_writes in
let unconditional_writes =
PathDomain.join astate1.unconditional_writes astate2.unconditional_writes in
let id_map = IdAccessPathMapDomain.join astate1.id_map astate2.id_map in let id_map = IdAccessPathMapDomain.join astate1.id_map astate2.id_map in
let owned = OwnershipDomain.join astate1.owned astate2.owned in let owned = OwnershipDomain.join astate1.owned astate2.owned in
{ locks; reads; writes; id_map; owned; } { locks; reads; conditional_writes; unconditional_writes; id_map; owned; }
let widen ~prev ~next ~num_iters = let widen ~prev ~next ~num_iters =
if phys_equal prev next if phys_equal prev next
@ -113,23 +130,28 @@ let widen ~prev ~next ~num_iters =
else else
let locks = LocksDomain.widen ~prev:prev.locks ~next:next.locks ~num_iters in let locks = LocksDomain.widen ~prev:prev.locks ~next:next.locks ~num_iters in
let reads = PathDomain.widen ~prev:prev.reads ~next:next.reads ~num_iters in let reads = PathDomain.widen ~prev:prev.reads ~next:next.reads ~num_iters in
let writes = PathDomain.widen ~prev:prev.writes ~next:next.writes ~num_iters in let conditional_writes =
ConditionalWritesDomain.widen
~prev:prev.conditional_writes ~next:next.conditional_writes ~num_iters in
let unconditional_writes =
PathDomain.widen ~prev:prev.unconditional_writes ~next:next.unconditional_writes ~num_iters in
let id_map = IdAccessPathMapDomain.widen ~prev:prev.id_map ~next:next.id_map ~num_iters in let id_map = IdAccessPathMapDomain.widen ~prev:prev.id_map ~next:next.id_map ~num_iters in
let owned = OwnershipDomain.widen ~prev:prev.owned ~next:next.owned ~num_iters in let owned = OwnershipDomain.widen ~prev:prev.owned ~next:next.owned ~num_iters in
{ locks; reads; writes; id_map; owned; } { locks; reads; conditional_writes; unconditional_writes; id_map; owned; }
let pp_summary fmt (locks, reads, writes) = let pp_summary fmt (locks, reads, conditional_writes, unconditional_writes) =
F.fprintf F.fprintf
fmt fmt
"Locks: %a Reads: %a Writes: %a" "Locks: %a Reads: %a Conditional Writes: %a Unconditional Writes: %a"
LocksDomain.pp locks LocksDomain.pp locks
PathDomain.pp reads PathDomain.pp reads
PathDomain.pp writes ConditionalWritesDomain.pp conditional_writes
PathDomain.pp unconditional_writes
let pp fmt { locks; reads; writes; id_map; owned; } = let pp fmt { locks; reads; conditional_writes; unconditional_writes; id_map; owned; } =
F.fprintf F.fprintf
fmt fmt
"%a Id Map: %a Owned: %a" "%a Id Map: %a Owned: %a"
pp_summary (locks, reads, writes) pp_summary (locks, reads, conditional_writes, unconditional_writes)
IdAccessPathMapDomain.pp id_map IdAccessPathMapDomain.pp id_map
OwnershipDomain.pp owned OwnershipDomain.pp owned

Loading…
Cancel
Save