(* * Copyright (c) 2016 - 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 Access = struct type t = | Read of AccessPath.t | Write of AccessPath.t | ContainerRead of AccessPath.t * Typ.Procname.t | ContainerWrite of AccessPath.t * Typ.Procname.t | InterfaceCall of Typ.Procname.t [@@deriving compare] let suffix_matches (_, accesses1) (_, accesses2) = match (List.rev accesses1, List.rev accesses2) with | access1 :: _, access2 :: _ -> AccessPath.equal_access access1 access2 | _ -> false let matches ~caller ~callee = match (caller, callee) with | Read ap1, Read ap2 | Write ap1, Write ap2 -> suffix_matches ap1 ap2 | ContainerRead (ap1, pname1), ContainerRead (ap2, pname2) | ContainerWrite (ap1, pname1), ContainerWrite (ap2, pname2) -> Typ.Procname.equal pname1 pname2 && suffix_matches ap1 ap2 | InterfaceCall pname1, InterfaceCall pname2 -> Typ.Procname.equal pname1 pname2 | _ -> false let make_field_access access_path ~is_write = if is_write then Write access_path else Read access_path let get_access_path = function | Read access_path | Write access_path | ContainerWrite (access_path, _) | ContainerRead (access_path, _) -> Some access_path | InterfaceCall _ -> None let map ~f = function | Read access_path -> Read (f access_path) | Write access_path -> Write (f access_path) | ContainerWrite (access_path, pname) -> ContainerWrite (f access_path, pname) | ContainerRead (access_path, pname) -> ContainerRead (f access_path, pname) | InterfaceCall _ as intfcall -> intfcall let equal t1 t2 = Int.equal (compare t1 t2) 0 let pp fmt = function | Read access_path -> F.fprintf fmt "Read of %a" AccessPath.pp access_path | Write access_path -> F.fprintf fmt "Write to %a" AccessPath.pp access_path | ContainerRead (access_path, pname) -> F.fprintf fmt "Read of container %a via %a" AccessPath.pp access_path Typ.Procname.pp pname | ContainerWrite (access_path, pname) -> F.fprintf fmt "Write to container %a via %a" AccessPath.pp access_path Typ.Procname.pp pname | InterfaceCall pname -> F.fprintf fmt "Call to un-annotated interface method %a" Typ.Procname.pp pname end module TraceElem = struct module Kind = Access type t = {site: CallSite.t; kind: Kind.t} [@@deriving compare] let is_write {kind} = match kind with | InterfaceCall _ | Read _ | ContainerRead _ -> false | ContainerWrite _ | Write _ -> true let is_container_write {kind} = match kind with | InterfaceCall _ | Read _ | Write _ | ContainerRead _ -> false | ContainerWrite _ -> true let call_site {site} = site let kind {kind} = kind let make ?indexes:_ kind site = {kind; site} let with_callsite t site = {t with site} let pp fmt {site; kind} = F.fprintf fmt "%a at %a" Access.pp kind CallSite.pp site let map ~f {site; kind} = {site; kind= Access.map ~f kind} module Set = PrettyPrintable.MakePPSet (struct type nonrec t = t let compare = compare let pp = pp end) let dummy_pname = Typ.Procname.empty_block let make_dummy_site = CallSite.make dummy_pname (* all trace elems are created with a dummy call site. any trace elem without a dummy call site represents a call that leads to an access rather than a direct access *) let is_direct {site} = Typ.Procname.equal (CallSite.pname site) dummy_pname let make_container_access access_path pname ~is_write loc = let site = make_dummy_site loc in let access = if is_write then Access.ContainerWrite (access_path, pname) else Access.ContainerRead (access_path, pname) in make access site let make_field_access access_path ~is_write loc = let site = make_dummy_site loc in make (Access.make_field_access access_path ~is_write) site let make_unannotated_call_access pname loc = let site = make_dummy_site loc in make (Access.InterfaceCall pname) site end (* In this domain true<=false. The intended denotations [[.]] are [[true]] = the set of all states where we know according, to annotations or assertions or lock instructions, that some lock is held. [[false]] = the empty set The use of && for join in this domain enforces that, to know a lock is held, one must hold in all branches. *) module LocksDomain = AbstractDomain.BooleanAnd module ThreadsDomain = struct type astate = NoThread | AnyThreadButSelf | AnyThread [@@deriving compare] let empty = NoThread (* NoThread < AnyThreadButSelf < Any *) let ( <= ) ~lhs ~rhs = match (lhs, rhs) with | NoThread, _ -> true | _, NoThread -> false | _, AnyThread -> true | AnyThread, _ -> false | _ -> Int.equal 0 (compare_astate lhs rhs) let join astate1 astate2 = match (astate1, astate2) with | NoThread, astate | astate, NoThread -> astate | AnyThread, _ | _, AnyThread -> AnyThread | AnyThreadButSelf, AnyThreadButSelf -> AnyThreadButSelf let widen ~prev ~next ~num_iters:_ = join prev next let pp fmt astate = F.fprintf fmt ( match astate with | NoThread -> "NoThread" | AnyThreadButSelf -> "AnyThreadButSelf" | AnyThread -> "AnyThread" ) let is_empty = function NoThread -> true | _ -> false let is_any_but_self = function AnyThreadButSelf -> true | _ -> false let is_any = function AnyThread -> true | _ -> false end module PathDomain = SinkTrace.Make (TraceElem) module Choice = struct type t = OnMainThread | LockHeld [@@deriving compare] let pp fmt = function | OnMainThread -> F.fprintf fmt "OnMainThread" | LockHeld -> F.fprintf fmt "LockHeld" end module Attribute = struct type t = Functional | Choice of Choice.t [@@deriving compare] let pp fmt = function | Functional -> F.fprintf fmt "Functional" | Choice choice -> Choice.pp fmt choice module Set = PrettyPrintable.MakePPSet (struct type nonrec t = t let compare = compare let pp = pp end) end module AttributeSetDomain = AbstractDomain.InvertedSet (Attribute) module OwnershipAbstractValue = struct type astate = Owned | OwnedIf of IntSet.t | Unowned [@@deriving compare] let owned = Owned let unowned = Unowned let make_owned_if formal_index = OwnedIf (IntSet.singleton formal_index) let ( <= ) ~lhs ~rhs = if phys_equal lhs rhs then true else match (lhs, rhs) with | _, Unowned -> true (* Unowned is top *) | Unowned, _ -> false | Owned, _ -> true (* Owned is bottom *) | OwnedIf s1, OwnedIf s2 -> IntSet.subset s1 s2 | OwnedIf _, Owned -> false let join astate1 astate2 = if phys_equal astate1 astate2 then astate1 else match (astate1, astate2) with | _, Unowned | Unowned, _ -> Unowned | astate, Owned | Owned, astate -> astate | OwnedIf s1, OwnedIf s2 -> OwnedIf (IntSet.union s1 s2) let widen ~prev ~next ~num_iters:_ = join prev next let pp fmt = function | Unowned -> F.fprintf fmt "Unowned" | OwnedIf s -> F.fprintf fmt "OwnedIf%a" (PrettyPrintable.pp_collection ~pp_item:Int.pp) (IntSet.elements s) | Owned -> F.fprintf fmt "Owned" end module OwnershipDomain = struct include AbstractDomain.Map (AccessPath) (OwnershipAbstractValue) let get_owned access_path astate = try find access_path astate with Not_found -> OwnershipAbstractValue.Unowned let is_owned access_path astate = match get_owned access_path astate with OwnershipAbstractValue.Owned -> true | _ -> false let find = `Use_get_owned_instead end module AttributeMapDomain = struct include AbstractDomain.InvertedMap (AccessPath) (AttributeSetDomain) let add access_path attribute_set t = if AttributeSetDomain.is_empty attribute_set then t else add access_path attribute_set t let has_attribute access_path attribute t = try find access_path t |> AttributeSetDomain.mem attribute with Not_found -> false let get_choices access_path t = try let attributes = find access_path t in List.filter_map ~f:(function Attribute.Choice c -> Some c | _ -> None) (AttributeSetDomain.elements attributes) with Not_found -> [] let add_attribute access_path attribute t = let attribute_set = (try find access_path t with Not_found -> AttributeSetDomain.empty) |> AttributeSetDomain.add attribute in add access_path attribute_set t end module Excluder = struct type t = Thread | Lock | Both [@@deriving compare] let pp fmt = function | Thread -> F.fprintf fmt "Thread" | Lock -> F.fprintf fmt "Lock" | Both -> F.fprintf fmt "both Thread and Lock" end module AccessPrecondition = struct type t = | Protected of Excluder.t | Unprotected of IntSet.t | TotallyUnprotected [@@deriving compare] let pp fmt = function | Protected excl -> F.fprintf fmt "ProtectedBy(%a)" Excluder.pp excl | TotallyUnprotected -> F.fprintf fmt "TotallyUnprotected" | Unprotected indexes -> F.fprintf fmt "Unprotected(%a)" (PrettyPrintable.pp_collection ~pp_item:Int.pp) (IntSet.elements indexes) let make_protected locks thread pdesc = let is_main_thread = ThreadsDomain.is_any_but_self thread in let locked = locks || Procdesc.is_java_synchronized pdesc in if not locked && not is_main_thread then TotallyUnprotected else if locked && is_main_thread then Protected Excluder.Both else if locked then Protected Excluder.Lock else Protected Excluder.Thread let make_unprotected indexes = assert (not (IntSet.is_empty indexes)) ; Unprotected indexes let totally_unprotected = TotallyUnprotected end module AccessDomain = struct include AbstractDomain.Map (AccessPrecondition) (PathDomain) let add_access precondition access_path t = let precondition_accesses = try find precondition t with Not_found -> PathDomain.empty in let precondition_accesses' = PathDomain.add_sink access_path precondition_accesses in add precondition precondition_accesses' t let get_accesses precondition t = try find precondition t with Not_found -> PathDomain.empty end type astate = { threads: ThreadsDomain.astate ; locks: LocksDomain.astate ; accesses: AccessDomain.astate ; ownership: OwnershipDomain.astate ; attribute_map: AttributeMapDomain.astate } let empty = let threads = ThreadsDomain.empty in let locks = false in let accesses = AccessDomain.empty in let ownership = OwnershipDomain.empty in let attribute_map = AttributeMapDomain.empty in {threads; locks; accesses; ownership; attribute_map} let is_empty {threads; locks; accesses; ownership; attribute_map} = ThreadsDomain.is_empty threads && not locks && AccessDomain.is_empty accesses && OwnershipDomain.is_empty ownership && AttributeMapDomain.is_empty attribute_map let ( <= ) ~lhs ~rhs = if phys_equal lhs rhs then true else ThreadsDomain.( <= ) ~lhs:lhs.threads ~rhs:rhs.threads && LocksDomain.( <= ) ~lhs:lhs.locks ~rhs:rhs.locks && AccessDomain.( <= ) ~lhs:lhs.accesses ~rhs:rhs.accesses && AttributeMapDomain.( <= ) ~lhs:lhs.attribute_map ~rhs:rhs.attribute_map let join astate1 astate2 = if phys_equal astate1 astate2 then astate1 else let threads = ThreadsDomain.join astate1.threads astate2.threads in let locks = LocksDomain.join astate1.locks astate2.locks in let accesses = AccessDomain.join astate1.accesses astate2.accesses in let ownership = OwnershipDomain.join astate1.ownership astate2.ownership in let attribute_map = AttributeMapDomain.join astate1.attribute_map astate2.attribute_map in {threads; locks; accesses; ownership; attribute_map} let widen ~prev ~next ~num_iters = if phys_equal prev next then prev else let threads = ThreadsDomain.widen ~prev:prev.threads ~next:next.threads ~num_iters in let locks = LocksDomain.widen ~prev:prev.locks ~next:next.locks ~num_iters in let accesses = AccessDomain.widen ~prev:prev.accesses ~next:next.accesses ~num_iters in let ownership = OwnershipDomain.widen ~prev:prev.ownership ~next:next.ownership ~num_iters in let attribute_map = AttributeMapDomain.widen ~prev:prev.attribute_map ~next:next.attribute_map ~num_iters in {threads; locks; accesses; ownership; attribute_map} type summary = { threads: ThreadsDomain.astate ; locks: LocksDomain.astate ; accesses: AccessDomain.astate ; return_ownership: OwnershipAbstractValue.astate ; return_attributes: AttributeSetDomain.astate } let pp_summary fmt {threads; locks; accesses; return_ownership; return_attributes} = F.fprintf fmt "@\nThreads: %a, Locks: %a @\nAccesses %a @\nOwnership: %a @\nReturn Attributes: %a @\n" ThreadsDomain.pp threads LocksDomain.pp locks AccessDomain.pp accesses OwnershipAbstractValue.pp return_ownership AttributeSetDomain.pp return_attributes let pp fmt {threads; locks; accesses; ownership; attribute_map} = F.fprintf fmt "Threads: %a, Locks: %a @\nAccesses %a @\n Ownership: %a @\nAttributes: %a @\n" ThreadsDomain.pp threads LocksDomain.pp locks AccessDomain.pp accesses OwnershipDomain.pp ownership AttributeMapDomain.pp attribute_map