[starvation] split events not under lock into separate state component

Summary: To simplify things, have a separate set of simple trace->event elements.  Currently this is not used for reporting, in preparation for another diff.

Reviewed By: mbouaziz

Differential Revision: D8203721

fbshipit-source-id: ecc8bae
master
Nikos Gorogiannis 7 years ago committed by Facebook Github Bot
parent e0e7c3cda4
commit 057efb2893

@ -220,21 +220,21 @@ end
let should_report_deadlock_on_current_proc current_elem endpoint_elem = let should_report_deadlock_on_current_proc current_elem endpoint_elem =
let open StarvationDomain in let open StarvationDomain in
match (current_elem.Order.first, current_elem.Order.eventually) with match (current_elem.Order.first, current_elem.Order.eventually) with
| None, _ | Some {Event.event= MayBlock _}, _ | _, {Event.event= MayBlock _} -> | None, _ | Some {Event.elem= MayBlock _}, _ | _, {Event.elem= MayBlock _} ->
(* should never happen *) (* should never happen *)
L.die InternalError "Deadlock cannot occur without two lock events: %a" Order.pp current_elem L.die InternalError "Deadlock cannot occur without two lock events: %a" Order.pp current_elem
| Some {Event.event= LockAcquire ((Var.LogicalVar _, _), [])}, _ -> | Some {Event.elem= LockAcquire ((Var.LogicalVar _, _), [])}, _ ->
(* first event is a class object (see [lock_of_class]), so always report because the (* first elem is a class object (see [lock_of_class]), so always report because the
reverse ordering on the events will not occur *) reverse ordering on the events will not occur *)
true true
| Some {Event.event= LockAcquire ((Var.LogicalVar _, _), _ :: _)}, _ | Some {Event.elem= LockAcquire ((Var.LogicalVar _, _), _ :: _)}, _
| _, {Event.event= LockAcquire ((Var.LogicalVar _, _), _)} -> | _, {Event.elem= LockAcquire ((Var.LogicalVar _, _), _)} ->
(* first event has an ident root, but has a non-empty access path, which means we are (* first elem has an ident root, but has a non-empty access path, which means we are
not filtering out local variables (see [exec_instr]), or, not filtering out local variables (see [exec_instr]), or,
second event has an ident root, which should not happen if we are filtering locals *) second elem has an ident root, which should not happen if we are filtering locals *)
L.die InternalError "Deadlock cannot occur on these logical variables: %a @." Order.pp L.die InternalError "Deadlock cannot occur on these logical variables: %a @." Order.pp
current_elem current_elem
| Some {Event.event= LockAcquire ((_, typ1), _)}, {Event.event= LockAcquire ((_, typ2), _)} -> | Some {Event.elem= LockAcquire ((_, typ1), _)}, {Event.elem= LockAcquire ((_, typ2), _)} ->
(* use string comparison on types as a stable order to decide whether to report a deadlock *) (* use string comparison on types as a stable order to decide whether to report a deadlock *)
let c = String.compare (Typ.to_string typ1) (Typ.to_string typ2) in let c = String.compare (Typ.to_string typ1) (Typ.to_string typ2) in
c < 0 c < 0
@ -264,7 +264,7 @@ let report_deadlocks tenv current_pdesc {StarvationDomain.order; ui} report_map'
else else
let () = debug "Possible deadlock:@.%a@.%a@." Order.pp current_elem Order.pp elem in let () = debug "Possible deadlock:@.%a@.%a@." Order.pp current_elem Order.pp elem in
match (current_elem.Order.eventually, elem.Order.eventually) with match (current_elem.Order.eventually, elem.Order.eventually) with
| {Event.event= LockAcquire _}, {Event.event= LockAcquire _} -> | {Event.elem= LockAcquire _}, {Event.elem= LockAcquire _} ->
let error_message = let error_message =
Format.asprintf Format.asprintf
"Potential deadlock.@.Trace 1 (starts at %a), %a.@.Trace 2 (starts at %a), %a." "Potential deadlock.@.Trace 1 (starts at %a), %a.@.Trace 2 (starts at %a), %a."
@ -283,12 +283,12 @@ let report_deadlocks tenv current_pdesc {StarvationDomain.order; ui} report_map'
in in
let report_on_current_elem elem report_map = let report_on_current_elem elem report_map =
match elem with match elem with
| {Order.first= None} | {Order.eventually= {Event.event= Event.MayBlock _}} -> | {Order.first= None} | {Order.eventually= {Event.elem= Event.MayBlock _}} ->
report_map report_map
| {Order.eventually= {Event.event= Event.LockAcquire endpoint_lock}} -> | {Order.eventually= {Event.elem= Event.LockAcquire endpoint_lock}} ->
Lock.owner_class endpoint_lock Lock.owner_class endpoint_lock
|> Option.value_map ~default:report_map ~f:(fun endpoint_class -> |> Option.value_map ~default:report_map ~f:(fun endpoint_class ->
(* get the class of the root variable of the lock in the endpoint event (* get the class of the root variable of the lock in the endpoint elem
and retrieve all the summaries of the methods of that class *) and retrieve all the summaries of the methods of that class *)
let endpoint_summaries = get_summaries_of_methods_in_class tenv endpoint_class in let endpoint_summaries = get_summaries_of_methods_in_class tenv endpoint_class in
(* for each summary related to the endpoint, analyse and report on its pairs *) (* for each summary related to the endpoint, analyse and report on its pairs *)
@ -303,14 +303,14 @@ let report_deadlocks tenv current_pdesc {StarvationDomain.order; ui} report_map'
OrderDomain.fold report_on_current_elem order report_map' OrderDomain.fold report_on_current_elem order report_map'
let report_blocks_on_main_thread tenv current_pdesc {StarvationDomain.order; ui} report_map' = let report_starvation tenv current_pdesc {StarvationDomain.order; ui} report_map' =
let open StarvationDomain in let open StarvationDomain in
let current_pname = Procdesc.get_proc_name current_pdesc in let current_pname = Procdesc.get_proc_name current_pdesc in
let report_remote_block ui_explain current_elem current_lock endpoint_pname endpoint_elem let report_remote_block ui_explain current_elem current_lock endpoint_pname endpoint_elem
report_map = report_map =
match endpoint_elem with match endpoint_elem with
| { Order.first= Some {Event.event= Event.LockAcquire lock} | { Order.first= Some {Event.elem= Event.LockAcquire lock}
; eventually= {Event.event= Event.MayBlock (block_descr, sev)} } ; eventually= {Event.elem= Event.MayBlock (block_descr, sev)} }
when Lock.equal current_lock lock -> when Lock.equal current_lock lock ->
let error_message = let error_message =
Format.asprintf Format.asprintf
@ -329,19 +329,19 @@ let report_blocks_on_main_thread tenv current_pdesc {StarvationDomain.order; ui}
in in
let report_on_current_elem ui_explain ({Order.eventually} as elem) report_map = let report_on_current_elem ui_explain ({Order.eventually} as elem) report_map =
match eventually with match eventually with
| {Event.event= Event.MayBlock (_, sev)} -> | {Event.elem= Event.MayBlock (_, sev)} ->
let error_message = let error_message =
Format.asprintf "Method %a runs on UI thread (because %s), and may block; %a." Format.asprintf "Method %a runs on UI thread (because %s), and may block; %a."
(MF.wrap_monospaced Typ.Procname.pp) (MF.wrap_monospaced Typ.Procname.pp)
current_pname ui_explain Event.pp_event eventually.Event.event current_pname ui_explain Event.pp_event eventually.Event.elem
in in
let loc = Order.get_loc elem in let loc = Order.get_loc elem in
let ltr = make_trace_with_header elem current_pname in let ltr = make_trace_with_header elem current_pname in
ReportMap.add_starvation sev current_pname loc ltr error_message report_map ReportMap.add_starvation sev current_pname loc ltr error_message report_map
| {Event.event= Event.LockAcquire endpoint_lock} -> | {Event.elem= Event.LockAcquire endpoint_lock} ->
Lock.owner_class endpoint_lock Lock.owner_class endpoint_lock
|> Option.value_map ~default:report_map ~f:(fun endpoint_class -> |> Option.value_map ~default:report_map ~f:(fun endpoint_class ->
(* get the class of the root variable of the lock in the endpoint event (* get the class of the root variable of the lock in the endpoint elem
and retrieve all the summaries of the methods of that class *) and retrieve all the summaries of the methods of that class *)
let endpoint_summaries = get_summaries_of_methods_in_class tenv endpoint_class in let endpoint_summaries = get_summaries_of_methods_in_class tenv endpoint_class in
(* for each summary related to the endpoint, analyse and report on its pairs *) (* for each summary related to the endpoint, analyse and report on its pairs *)
@ -367,7 +367,7 @@ let reporting {Callbacks.procedures; exe_env} =
Payload.read proc_desc (Procdesc.get_proc_name proc_desc) Payload.read proc_desc (Procdesc.get_proc_name proc_desc)
|> Option.iter ~f:(fun summary -> |> Option.iter ~f:(fun summary ->
report_deadlocks tenv proc_desc summary ReportMap.empty report_deadlocks tenv proc_desc summary ReportMap.empty
|> report_blocks_on_main_thread tenv proc_desc summary |> ReportMap.log ) |> report_starvation tenv proc_desc summary |> ReportMap.log )
in in
List.iter procedures ~f:report_procedure ; List.iter procedures ~f:report_procedure ;
let sourcefile = exe_env.Exe_env.source_file in let sourcefile = exe_env.Exe_env.source_file in

@ -52,6 +52,64 @@ module Lock = struct
let owner_class ((_, typ), _) = Typ.inner_name typ let owner_class ((_, typ), _) = Typ.inner_name typ
end end
module type TraceElem = sig
type elem_t
type t = private {elem: elem_t; loc: Location.t; trace: CallSite.t list}
include PrettyPrintable.PrintableOrderedType with type t := t
val make : elem_t -> Location.t -> t
val get_loc : t -> Location.t
val make_loc_trace : ?reverse:bool -> t -> Errlog.loc_trace
val with_callsite : t -> CallSite.t -> t
end
module MakeTraceElem (Elem : PrettyPrintable.PrintableOrderedType) :
TraceElem with type elem_t = Elem.t =
struct
type elem_t = Elem.t
type t = {elem: Elem.t; loc: Location.t; trace: CallSite.t list [@compare.ignore]}
[@@deriving compare]
let pp fmt e =
let pp_trace fmt = function
| [] ->
()
| trace ->
F.fprintf fmt " (trace: %a)" (Pp.semicolon_seq CallSite.pp) trace
in
F.fprintf fmt "%a at %a%a" Elem.pp e.elem Location.pp e.loc pp_trace e.trace
let make elem loc = {elem; loc; trace= []}
let get_loc {loc; trace} = List.hd trace |> Option.value_map ~default:loc ~f:CallSite.loc
let make_loc_trace ?(reverse= false) e =
let call_trace, nesting =
List.fold e.trace ~init:([], 0) ~f:(fun (tr, ns) callsite ->
let elem_descr =
F.asprintf "Method call: %a"
(MF.wrap_monospaced Typ.Procname.pp)
(CallSite.pname callsite)
in
let elem = Errlog.make_trace_element ns (CallSite.loc callsite) elem_descr [] in
(elem :: tr, ns + 1) )
in
let endpoint_descr = F.asprintf "%a" Elem.pp e.elem in
let endpoint = Errlog.make_trace_element nesting e.loc endpoint_descr [] in
let res = endpoint :: call_trace in
if reverse then res else List.rev res
let with_callsite elem callsite = {elem with trace= callsite :: elem.trace}
end
module Event = struct module Event = struct
type severity_t = Low | Medium | High [@@deriving compare] type severity_t = Low | Medium | High [@@deriving compare]
@ -64,20 +122,16 @@ module Event = struct
F.pp_print_string fmt msg F.pp_print_string fmt msg
type t = {event: event_t; loc: Location.t; trace: CallSite.t list} include MakeTraceElem (struct
type t = event_t [@@deriving compare]
let is_lock_event e = match e.event with LockAcquire _ -> true | _ -> false let pp = pp_event
end)
(* ignore trace when comparing *)
let compare e e' =
if phys_equal e e' then 0
else
let res = compare_event_t e.event e'.event in
if not (Int.equal res 0) then res else Location.compare e.loc e'.loc
let is_lock_event e = match e.elem with LockAcquire _ -> true | _ -> false
let locks_equal e e' = let locks_equal e e' =
match (e.event, e'.event) with match (e.elem, e'.elem) with
| LockAcquire lock, LockAcquire lock' -> | LockAcquire lock, LockAcquire lock' ->
Lock.equal lock lock' Lock.equal lock lock'
| _, _ -> | _, _ ->
@ -85,26 +139,14 @@ module Event = struct
let locks_equal_modulo_base e e' = let locks_equal_modulo_base e e' =
match (e.event, e'.event) with match (e.elem, e'.elem) with
| LockAcquire lock, LockAcquire lock' -> | LockAcquire lock, LockAcquire lock' ->
Lock.equal_modulo_base lock lock' Lock.equal_modulo_base lock lock'
| _, _ -> | _, _ ->
false false
let pp fmt e = let make_acquire lock loc = make (LockAcquire lock) loc
let pp_trace fmt = function
| [] ->
()
| trace ->
F.fprintf fmt " (trace: %a)" (Pp.semicolon_seq CallSite.pp) trace
in
F.fprintf fmt "%a at %a%a" pp_event e.event Location.pp e.loc pp_trace e.trace
let make_acquire lock loc = {event= LockAcquire lock; loc; trace= []}
let make_blocks msg sev loc = {event= MayBlock (msg, sev); loc; trace= []}
let make_blocking_call ~caller ~callee sev loc = let make_blocking_call ~caller ~callee sev loc =
let descr = let descr =
@ -114,26 +156,14 @@ module Event = struct
(MF.wrap_monospaced Typ.Procname.pp) (MF.wrap_monospaced Typ.Procname.pp)
caller caller
in in
make_blocks descr sev loc make (MayBlock (descr, sev)) loc
end
let get_loc {loc; trace} = List.hd trace |> Option.value_map ~default:loc ~f:CallSite.loc module EventDomain = struct
include AbstractDomain.FiniteSet (Event)
let make_loc_trace ?(reverse= false) e = let with_callsite astate callsite =
let call_trace, nesting = fold (fun e acc -> add (Event.with_callsite e callsite) acc) astate empty
List.fold e.trace ~init:([], 0) ~f:(fun (tr, ns) callsite ->
let elem_descr =
F.asprintf "Method call: %a"
(MF.wrap_monospaced Typ.Procname.pp)
(CallSite.pname callsite)
in
let elem = Errlog.make_trace_element ns (CallSite.loc callsite) elem_descr [] in
(elem :: tr, ns + 1) )
in
let endpoint_descr = F.asprintf "%a" pp_event e.event in
let endpoint = Errlog.make_trace_element nesting e.loc endpoint_descr [] in
let res = endpoint :: call_trace in
if reverse then res else List.rev res
end end
module Order = struct module Order = struct
@ -159,16 +189,14 @@ module Order = struct
let make_eventually eventually = {first= None; eventually} let make_eventually eventually = {first= None; eventually}
let make_first_and_eventually b eventually = let make_first_and_eventually b eventually =
if not (Event.is_lock_event b) then L.(die InternalError) "Expected a lock event first." ; if not (Event.is_lock_event b) then L.(die InternalError) "Expected a lock elem first." ;
{first= Some b; eventually} {first= Some b; eventually}
let with_callsite callsite o = let with_callsite callsite o = {o with eventually= Event.with_callsite o.eventually callsite}
{o with eventually= {o.eventually with Event.trace= callsite :: o.eventually.Event.trace}}
let get_loc {first; eventually} = let get_loc {first; eventually} =
match first with Some event -> Event.get_loc event | None -> Event.get_loc eventually match first with Some elem -> Event.get_loc elem | None -> Event.get_loc eventually
let make_loc_trace o = let make_loc_trace o =
@ -197,7 +225,7 @@ module LockState = struct
include AbstractDomain.InvertedMap (Lock) (LockStack) include AbstractDomain.InvertedMap (Lock) (LockStack)
let is_taken lock_event map = let is_taken lock_event map =
match lock_event.Event.event with match lock_event.Event.elem with
| Event.LockAcquire lock -> ( | Event.LockAcquire lock -> (
try not (find lock map |> LockStack.is_empty) with Caml.Not_found -> false ) try not (find lock map |> LockStack.is_empty) with Caml.Not_found -> false )
| _ -> | _ ->
@ -237,21 +265,32 @@ end
module UIThreadDomain = AbstractDomain.BottomLifted (UIThreadExplanationDomain) module UIThreadDomain = AbstractDomain.BottomLifted (UIThreadExplanationDomain)
type astate = {lock_state: LockState.astate; order: OrderDomain.astate; ui: UIThreadDomain.astate} type astate =
{ lock_state: LockState.astate
; events: EventDomain.astate
; order: OrderDomain.astate
; ui: UIThreadDomain.astate }
let empty =
{ lock_state= LockState.empty
; events= EventDomain.empty
; order= OrderDomain.empty
; ui= UIThreadDomain.empty }
let empty = {lock_state= LockState.empty; order= OrderDomain.empty; ui= UIThreadDomain.empty}
let is_empty {lock_state; order; ui} = let is_empty {lock_state; events; order; ui} =
LockState.is_empty lock_state && OrderDomain.is_empty order && UIThreadDomain.is_empty ui UIThreadDomain.is_empty ui && EventDomain.is_empty events && OrderDomain.is_empty order
&& LockState.is_empty lock_state
let pp fmt {lock_state; order; ui} = let pp fmt {lock_state; events; order; ui} =
F.fprintf fmt "{lock_state= %a; order= %a; ui= %a}" LockState.pp lock_state OrderDomain.pp order F.fprintf fmt "{lock_state= %a; events= %a; order= %a; ui= %a}" LockState.pp lock_state
UIThreadDomain.pp ui EventDomain.pp events OrderDomain.pp order UIThreadDomain.pp ui
let join lhs rhs = let join lhs rhs =
{ lock_state= LockState.join lhs.lock_state rhs.lock_state { lock_state= LockState.join lhs.lock_state rhs.lock_state
; events= EventDomain.join lhs.events rhs.events
; order= OrderDomain.join lhs.order rhs.order ; order= OrderDomain.join lhs.order rhs.order
; ui= UIThreadDomain.join lhs.ui rhs.ui } ; ui= UIThreadDomain.join lhs.ui rhs.ui }
@ -259,7 +298,9 @@ let join lhs rhs =
let widen ~prev ~next ~num_iters:_ = join prev next let widen ~prev ~next ~num_iters:_ = join prev next
let ( <= ) ~lhs ~rhs = let ( <= ) ~lhs ~rhs =
UIThreadDomain.( <= ) ~lhs:lhs.ui ~rhs:rhs.ui && OrderDomain.( <= ) ~lhs:lhs.order ~rhs:rhs.order UIThreadDomain.( <= ) ~lhs:lhs.ui ~rhs:rhs.ui
&& EventDomain.( <= ) ~lhs:lhs.events ~rhs:rhs.events
&& OrderDomain.( <= ) ~lhs:lhs.order ~rhs:rhs.order
&& LockState.( <= ) ~lhs:lhs.lock_state ~rhs:rhs.lock_state && LockState.( <= ) ~lhs:lhs.lock_state ~rhs:rhs.lock_state
@ -283,25 +324,28 @@ let add_order_pairs order lock_event acc =
LockState.fold_over_events add_first_and_eventually order acc |> add_eventually LockState.fold_over_events add_first_and_eventually order acc |> add_eventually
let acquire ({lock_state; order} as astate) loc lockid = let acquire ({lock_state; events; order} as astate) loc lockid =
let newlock_event = Event.make_acquire lockid loc in let new_event = Event.make_acquire lockid loc in
{ astate with { astate with
lock_state= LockState.acquire lockid newlock_event lock_state lock_state= LockState.acquire lockid new_event lock_state
; order= add_order_pairs lock_state newlock_event order } ; events= EventDomain.add new_event events
; order= add_order_pairs lock_state new_event order }
let blocking_call ~caller ~callee sev loc ({lock_state; order} as astate) = let blocking_call ~caller ~callee sev loc ({lock_state; events; order} as astate) =
let newlock_event = Event.make_blocking_call ~caller ~callee sev loc in let new_event = Event.make_blocking_call ~caller ~callee sev loc in
{astate with order= add_order_pairs lock_state newlock_event order} { astate with
events= EventDomain.add new_event events; order= add_order_pairs lock_state new_event order }
let release ({lock_state} as astate) lockid = let release ({lock_state} as astate) lockid =
{astate with lock_state= LockState.release lockid lock_state} {astate with lock_state= LockState.release lockid lock_state}
let integrate_summary ({lock_state; order; ui} as astate) callee_pname loc callee_summary = let integrate_summary ({lock_state; events; order; ui} as astate) callee_pname loc callee_summary =
let callee_order = callee_summary.order in let callee_order = callee_summary.order in
let callee_ui = callee_summary.ui in let callee_ui = callee_summary.ui in
let callee_events = callee_summary.events in
(* for each pair (b,a) in the callee, add (l,b) and (l,a) to the current state, where (* for each pair (b,a) in the callee, add (l,b) and (l,a) to the current state, where
l is held locally *) l is held locally *)
let do_elem elem acc = let do_elem elem acc =
@ -311,7 +355,10 @@ let integrate_summary ({lock_state; order; ui} as astate) callee_pname loc calle
let callsite = CallSite.make callee_pname loc in let callsite = CallSite.make callee_pname loc in
(* add callsite to the "eventually" trace *) (* add callsite to the "eventually" trace *)
let elems = OrderDomain.with_callsite callsite callee_order in let elems = OrderDomain.with_callsite callsite callee_order in
{astate with order= OrderDomain.fold do_elem elems order; ui= UIThreadDomain.join ui callee_ui} { astate with
events= EventDomain.join events (EventDomain.with_callsite callee_events callsite)
; order= OrderDomain.fold do_elem elems order
; ui= UIThreadDomain.join ui callee_ui }
let set_on_ui_thread ({ui} as astate) explain = let set_on_ui_thread ({ui} as astate) explain =

@ -29,11 +29,13 @@ module Event : sig
val pp_event : F.formatter -> event_t -> unit val pp_event : F.formatter -> event_t -> unit
type t = private {event: event_t; loc: Location.t; trace: CallSite.t list} type t = private {elem: event_t; loc: Location.t; trace: CallSite.t list}
include PrettyPrintable.PrintableOrderedType with type t := t include PrettyPrintable.PrintableOrderedType with type t := t
end end
module EventDomain : module type of AbstractDomain.FiniteSet (Event)
(** Represents either (** Represents either
- the existence of a program path from the current method to the eventual acquisition of a lock - the existence of a program path from the current method to the eventual acquisition of a lock
("eventually"), or, ("eventually"), or,
@ -64,7 +66,11 @@ module LockState : AbstractDomain.WithBottom
module UIThreadDomain : module UIThreadDomain :
AbstractDomain.WithBottom with type astate = string AbstractDomain.Types.bottom_lifted AbstractDomain.WithBottom with type astate = string AbstractDomain.Types.bottom_lifted
type astate = {lock_state: LockState.astate; order: OrderDomain.astate; ui: UIThreadDomain.astate} type astate =
{ lock_state: LockState.astate
; events: EventDomain.astate
; order: OrderDomain.astate
; ui: UIThreadDomain.astate }
include AbstractDomain.WithBottom with type astate := astate include AbstractDomain.WithBottom with type astate := astate

Loading…
Cancel
Save