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.

229 lines
8.2 KiB

(*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
open! IStd
module F = Format
(** Domain for thread-type. The main goals are
- Track code paths that are explicitly on UI/BG thread (via annotations, or assertions).
- Maintain UI/BG-thread-ness through the call stack (if a caller is of unknown status and callee
is on UI/BG thread then caller must be on the UI/BG thread too).
- Traces with "UI-thread" status cannot interleave but all other combinations can.
- Top is AnyThread, which means that there are executions on both UI and BG threads on this
method.
- Bottom is UnknownThread, and used as initial state. *)
module ThreadDomain : sig
type t = UnknownThread | UIThread | BGThread | AnyThread
include AbstractDomain.WithBottom with type t := t
end
(** Abstract address for a lock. There are two notions of equality:
- Equality for comparing two addresses within the same thread/process/trace. Under this,
identical globals and identical class objects compare equal. Locks represented by access paths
rooted at method parameters must have equal access paths to compare equal. Paths rooted at
locals are ignored.
- Equality for comparing two addresses in two distinct threads/traces. Globals and class objects
are compared in the same way, but locks represented by access paths rooted at parameters need
only have equal access lists (ie [x.f.g == y.f.g]). This allows demonically aliasing
parameters in *distinct* threads. This relation is used in [may_deadlock]. *)
module Lock : sig
include module type of AbstractAddress
val pp_locks : F.formatter -> t -> unit
val make_java_synchronized : FormalMap.t -> Procname.t -> t option
(** create the monitor locked when entering a synchronized java method *)
val compare_wrt_reporting : t -> t -> int
(** a stable order for avoiding reporting deadlocks twice based on the root variable type *)
end
module Event : sig
type t =
| LockAcquire of Lock.t
| MayBlock of (Procname.t * StarvationModels.severity)
| StrictModeCall of Procname.t
| MonitorWait of Lock.t
[@@deriving compare]
val describe : F.formatter -> t -> unit
end
module LockState : AbstractDomain.WithTop
(** a lock acquisition with location information *)
module Acquisition : sig
type t = private
{lock: Lock.t; loc: Location.t [@compare.ignore]; procname: Procname.t [@compare.ignore]}
[@@deriving compare]
end
(** A set of lock acquisitions with source locations and procnames. *)
module Acquisitions : sig
include PrettyPrintable.PPSet with type elt = Acquisition.t
val lock_is_held : Lock.t -> t -> bool
(** is the given lock in the set *)
val lock_is_held_in_other_thread : Tenv.t -> Lock.t -> t -> bool
(** is the given lock held, modulo memory abstraction across threads *)
end
(** An event and the currently-held locks at the time it occurred. *)
module CriticalPairElement : sig
type t = private {acquisitions: Acquisitions.t; event: Event.t; thread: ThreadDomain.t}
end
(** A [CriticalPairElement] equipped with a call stack. The intuition is that if we have a critical
pair `(locks, event)` in the summary of a method then there is a trace of that method where
`event` occurs, and right before it occurs the locks held are exactly `locks` (no over/under
approximation). We call it "critical" because the information here alone determines deadlock
conditions. *)
module CriticalPair : sig
type t = private {elem: CriticalPairElement.t; loc: Location.t; trace: CallSite.t list}
include PrettyPrintable.PrintableOrderedType with type t := t
val get_loc : t -> Location.t
(** outermost callsite location *)
val get_earliest_lock_or_call_loc : procname:Procname.t -> t -> Location.t
(** outermost callsite location OR lock acquisition *)
val may_deadlock : Tenv.t -> t -> t -> bool
(** two pairs can run in parallel and satisfy the conditions for deadlock *)
val make_trace :
?header:string -> ?include_acquisitions:bool -> Procname.t -> t -> Errlog.loc_trace
val is_uithread : t -> bool
(** is pair about an event on the UI thread *)
val can_run_in_parallel : t -> t -> bool
(** can two pairs describe events on two threads that can run in parallel *)
end
module CriticalPairs : AbstractDomain.FiniteSetS with type elt = CriticalPair.t
module GuardToLockMap : AbstractDomain.WithTop
(** Tracks expression attributes *)
module Attribute : sig
type t =
| Nothing
| ThreadGuard (** is boolean equivalent to whether on UI thread *)
| FutureDoneGuard of HilExp.AccessExpression.t (** boolean equivalent to [Future.isDone()] *)
| FutureDoneState of bool (** is a [Future] ready for non-blocking consumption *)
| Runnable of Procname.t (** is a Runnable/Callable with given "run" procname *)
| WorkScheduler of StarvationModels.scheduler_thread_constraint
(** exp is something that schedules work on the given thread *)
| Looper of StarvationModels.scheduler_thread_constraint (** Android looper on given thread *)
include AbstractDomain.WithTop with type t := t
end
(** Tracks all expressions assigned values of [Attribute] *)
module AttributeDomain : sig
include
AbstractDomain.InvertedMapS
with type key = HilExp.AccessExpression.t
and type value = Attribute.t
val is_thread_guard : HilExp.AccessExpression.t -> t -> bool
val is_future_done_guard : HilExp.AccessExpression.t -> t -> bool
(** does the given expr has attribute [FutureDone x] return [Some x] else [None] *)
val exit_scope : Var.t list -> t -> t
end
(** A record of scheduled parallel work: the method scheduled to run, where, and on what thread. *)
module ScheduledWorkItem : sig
type t = {procname: Procname.t; loc: Location.t; thread: ThreadDomain.t}
include PrettyPrintable.PrintableOrderedType with type t := t
end
module ScheduledWorkDomain : AbstractDomain.FiniteSetS with type elt = ScheduledWorkItem.t
type t =
{ guard_map: GuardToLockMap.t
; lock_state: LockState.t
; critical_pairs: CriticalPairs.t
; attributes: AttributeDomain.t
; thread: ThreadDomain.t
; scheduled_work: ScheduledWorkDomain.t }
include AbstractDomain.WithBottom with type t := t
val acquire : ?tenv:Tenv.t -> t -> procname:Procname.t -> loc:Location.t -> Lock.t list -> t
(** simultaneously acquire a number of locks, no-op if list is empty *)
val release : t -> Lock.t list -> t
(** simultaneously release a number of locks, no-op if list is empty *)
val blocking_call : callee:Procname.t -> StarvationModels.severity -> loc:Location.t -> t -> t
val wait_on_monitor : loc:Location.t -> FormalMap.t -> HilExp.t list -> t -> t
val future_get : callee:Procname.t -> loc:Location.t -> HilExp.t list -> t -> t
val strict_mode_call : callee:Procname.t -> loc:Location.t -> t -> t
val add_guard :
acquire_now:bool
-> procname:Procname.t
-> loc:Location.t
-> Tenv.t
-> t
-> HilExp.t
-> Lock.t
-> t
(** Install a mapping from the guard expression to the lock provided, and optionally lock it. *)
val lock_guard : procname:Procname.t -> loc:Location.t -> Tenv.t -> t -> HilExp.t -> t
(** Acquire the lock the guard was constructed with. *)
val remove_guard : t -> HilExp.t -> t
(** Destroy the guard and release its lock. *)
val unlock_guard : t -> HilExp.t -> t
(** Release the lock the guard was constructed with. *)
val schedule_work :
Location.t -> StarvationModels.scheduler_thread_constraint -> t -> Procname.t -> t
(** record the fact that a method is scheduled to run on a certain thread/executor *)
type summary =
{ critical_pairs: CriticalPairs.t
; thread: ThreadDomain.t
; scheduled_work: ScheduledWorkDomain.t
; attributes: AttributeDomain.t (** final-state attributes that affect instance variables only *)
; return_attribute: Attribute.t }
val empty_summary : summary
val pp_summary : F.formatter -> summary -> unit
val integrate_summary :
?tenv:Tenv.t
-> ?lhs:HilExp.AccessExpression.t
-> ?subst:Lock.subst
-> CallSite.t
-> t
-> summary
-> t
(** apply a callee summary to the current abstract state; [lhs] is the expression assigned the
returned value, if any *)
val summary_of_astate : Procdesc.t -> t -> summary
val filter_blocking_calls : t -> t