(* * Copyright (c) 2018 - 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! IStd module F = Format module L = Logging module MF = MarkupFormatter module Lock = struct type t = AccessPath.t (* compare type, base variable modulo this and access list *) let compare (((base, typ), aclist) as lock) (((base', typ'), aclist') as lock') = if phys_equal lock lock' then 0 else let res = Typ.compare typ typ' in if not (Int.equal res 0) then res else let res = Var.compare_modulo_this base base' in if not (Int.equal res 0) then res else List.compare AccessPath.compare_access aclist aclist' let equal lock lock' = Int.equal 0 (compare lock lock') let equal_modulo_base (((root, typ), aclist) as l) (((root', typ'), aclist') as l') = if phys_equal l l' then true else match (root, root') with | Var.LogicalVar _, Var.LogicalVar _ -> (* only class objects are supposed to appear as idents *) equal l l' | Var.ProgramVar _, Var.ProgramVar _ -> Typ.equal typ typ' && AccessPath.equal_access_list aclist aclist' | _, _ -> false let pp fmt (((_, typ), _) as lock) = F.fprintf fmt "locks %a in class %a" (MF.wrap_monospaced AccessPath.pp) lock (MF.wrap_monospaced (Typ.pp_full Pp.text)) typ let owner_class ((_, typ), _) = Typ.inner_name typ end module Event = struct type severity_t = Low | Medium | High [@@deriving compare] type event_t = LockAcquire of Lock.t | MayBlock of (string * severity_t) [@@deriving compare] let pp_event fmt = function | LockAcquire lock -> Lock.pp fmt lock | MayBlock (msg, _) -> F.pp_print_string fmt msg type t = {event: event_t; loc: Location.t; trace: CallSite.t list} let is_lock_event e = match e.event with LockAcquire _ -> true | _ -> false (* 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 locks_equal e e' = match (e.event, e'.event) with | LockAcquire lock, LockAcquire lock' -> Lock.equal lock lock' | _, _ -> false let locks_equal_modulo_base e e' = match (e.event, e'.event) with | LockAcquire lock, LockAcquire lock' -> Lock.equal_modulo_base lock lock' | _, _ -> false 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" 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 descr = F.asprintf "calls %a from %a" (MF.wrap_monospaced Typ.Procname.pp) callee (MF.wrap_monospaced Typ.Procname.pp) caller in make_blocks descr sev loc 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" 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 module Order = struct type t = {first: Event.t option; eventually: Event.t} [@@deriving compare] let pp fmt o = match o.first with | None -> F.fprintf fmt "eventually %a" Event.pp o.eventually | Some lock -> F.fprintf fmt "first %a, and before releasing it, %a" Event.pp lock Event.pp o.eventually let may_deadlock elem elem' = match (elem.first, elem'.first) with | Some b, Some b' -> Event.locks_equal_modulo_base b elem'.eventually && Event.locks_equal_modulo_base b' elem.eventually | _, _ -> false let make_eventually eventually = {first= None; eventually} let make_first_and_eventually b eventually = if not (Event.is_lock_event b) then L.(die InternalError) "Expected a lock event first." ; {first= Some b; eventually} let with_callsite callsite o = {o with eventually= {o.eventually with Event.trace= callsite :: o.eventually.Event.trace}} let get_loc {first; eventually} = match first with Some event -> Event.get_loc event | None -> Event.get_loc eventually let make_loc_trace o = let first_trace = Option.value_map o.first ~default:[] ~f:(Event.make_loc_trace ~reverse:true) in let eventually_trace = Event.make_loc_trace o.eventually in List.rev_append first_trace eventually_trace end module OrderDomain = struct include AbstractDomain.FiniteSet (Order) let with_callsite callsite lo = fold (fun o acc -> add (Order.with_callsite callsite o) acc) lo empty let is_eventually_locked lock lo = Event.is_lock_event lock && exists (fun pair -> Event.locks_equal pair.Order.eventually lock) lo end module LockStack = AbstractDomain.StackDomain (Event) module LockState = struct include AbstractDomain.InvertedMap (Lock) (LockStack) let is_taken lock_event map = match lock_event.Event.event with | Event.LockAcquire lock -> ( try not (find lock map |> LockStack.is_empty) with Caml.Not_found -> false ) | _ -> false let acquire lock_id lock_event map = let current_value = try find lock_id map with Caml.Not_found -> LockStack.empty in let new_value = LockStack.push lock_event current_value in add lock_id new_value map let release lock_id map = let current_value = try find lock_id map with Caml.Not_found -> LockStack.empty in if LockStack.is_empty current_value then map else let new_value = LockStack.pop current_value in if LockStack.is_empty new_value then remove lock_id map else add lock_id new_value map let fold_over_events f map init = let ff _ lock_state acc = List.fold lock_state ~init:acc ~f in fold ff map init end module UIThreadExplanationDomain = struct type astate = string let pp = String.pp let join lhs _ = lhs let widen ~prev ~next ~num_iters:_ = join prev next let ( <= ) ~lhs:_ ~rhs:_ = true end module UIThreadDomain = AbstractDomain.BottomLifted (UIThreadExplanationDomain) type astate = {lock_state: LockState.astate; order: OrderDomain.astate; ui: UIThreadDomain.astate} let empty = {lock_state= LockState.empty; order= OrderDomain.empty; ui= UIThreadDomain.empty} let is_empty {lock_state; order; ui} = LockState.is_empty lock_state && OrderDomain.is_empty order && UIThreadDomain.is_empty ui let pp fmt {lock_state; order; ui} = F.fprintf fmt "{lock_state= %a; order= %a; ui= %a}" LockState.pp lock_state OrderDomain.pp order UIThreadDomain.pp ui let join lhs rhs = { lock_state= LockState.join lhs.lock_state rhs.lock_state ; order= OrderDomain.join lhs.order rhs.order ; ui= UIThreadDomain.join lhs.ui rhs.ui } let widen ~prev ~next ~num_iters:_ = join prev next let ( <= ) ~lhs ~rhs = UIThreadDomain.( <= ) ~lhs:lhs.ui ~rhs:rhs.ui && OrderDomain.( <= ) ~lhs:lhs.order ~rhs:rhs.order && LockState.( <= ) ~lhs:lhs.lock_state ~rhs:rhs.lock_state (* for every lock b held locally, add a pair (b, lock_event), plus (None, lock_event) *) let add_order_pairs order lock_event acc = (* add no pairs whatsoever if we already hold that lock *) if LockState.is_taken lock_event order then acc else let add_eventually acc = (* don't add an eventually-locks pair if there is already another with same endpoint*) if OrderDomain.is_eventually_locked lock_event acc then acc else let elem = Order.make_eventually lock_event in OrderDomain.add elem acc in let add_first_and_eventually acc first = (* never add a pair of the form (a,a) -- should never happen due to the check above *) let elem = Order.make_first_and_eventually first lock_event in OrderDomain.add elem acc in LockState.fold_over_events add_first_and_eventually order acc |> add_eventually let acquire ({lock_state; order} as astate) loc lockid = let newlock_event = Event.make_acquire lockid loc in { astate with lock_state= LockState.acquire lockid newlock_event lock_state ; order= add_order_pairs lock_state newlock_event order } let blocking_call ~caller ~callee sev loc ({lock_state; order} as astate) = let newlock_event = Event.make_blocking_call ~caller ~callee sev loc in {astate with order= add_order_pairs lock_state newlock_event order} let release ({lock_state} as astate) lockid = {astate with lock_state= LockState.release lockid lock_state} let integrate_summary ({lock_state; order; ui} as astate) callee_pname loc callee_summary = let callee_order = callee_summary.order in let callee_ui = callee_summary.ui in (* for each pair (b,a) in the callee, add (l,b) and (l,a) to the current state, where l is held locally *) let do_elem elem acc = Option.value_map elem.Order.first ~default:acc ~f:(fun b -> add_order_pairs lock_state b acc) |> add_order_pairs lock_state elem.Order.eventually in let callsite = CallSite.make callee_pname loc in (* add callsite to the "eventually" trace *) let elems = OrderDomain.with_callsite callsite callee_order in {astate with order= OrderDomain.fold do_elem elems order; ui= UIThreadDomain.join ui callee_ui} let set_on_ui_thread ({ui} as astate) explain = {astate with ui= UIThreadDomain.join ui (AbstractDomain.Types.NonBottom explain)} type summary = astate let pp_summary = pp