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.
430 lines
13 KiB
430 lines
13 KiB
8 years ago
|
(*
|
||
|
* 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.
|
||
|
*)
|
||
8 years ago
|
|
||
8 years ago
|
open! IStd
|
||
8 years ago
|
module F = Format
|
||
|
|
||
8 years ago
|
module Access = struct
|
||
8 years ago
|
type t =
|
||
8 years ago
|
| Read of AccessPath.t
|
||
|
| Write of AccessPath.t
|
||
|
| ContainerRead of AccessPath.t * Typ.Procname.t
|
||
|
| ContainerWrite of AccessPath.t * Typ.Procname.t
|
||
8 years ago
|
| InterfaceCall of Typ.Procname.t
|
||
|
[@@deriving compare]
|
||
8 years ago
|
|
||
8 years ago
|
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
|
||
|
|
||
8 years ago
|
let make_field_access access_path ~is_write =
|
||
|
if is_write then Write access_path else Read access_path
|
||
8 years ago
|
|
||
8 years ago
|
let get_access_path = function
|
||
8 years ago
|
| Read access_path
|
||
|
| Write access_path
|
||
|
| ContainerWrite (access_path, _)
|
||
|
| ContainerRead (access_path, _)
|
||
8 years ago
|
-> Some access_path
|
||
|
| InterfaceCall _
|
||
|
-> None
|
||
8 years ago
|
|
||
8 years ago
|
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
|
||
|
|
||
8 years ago
|
let equal t1 t2 = Int.equal (compare t1 t2) 0
|
||
|
|
||
8 years ago
|
let pp fmt = function
|
||
|
| Read access_path
|
||
8 years ago
|
-> F.fprintf fmt "Read of %a" AccessPath.pp access_path
|
||
8 years ago
|
| Write access_path
|
||
8 years ago
|
-> F.fprintf fmt "Write to %a" AccessPath.pp access_path
|
||
8 years ago
|
| ContainerRead (access_path, pname)
|
||
8 years ago
|
-> F.fprintf fmt "Read of container %a via %a" AccessPath.pp access_path Typ.Procname.pp pname
|
||
8 years ago
|
| ContainerWrite (access_path, pname)
|
||
8 years ago
|
-> F.fprintf fmt "Write to container %a via %a" AccessPath.pp access_path Typ.Procname.pp
|
||
8 years ago
|
pname
|
||
8 years ago
|
| InterfaceCall pname
|
||
|
-> F.fprintf fmt "Call to un-annotated interface method %a" Typ.Procname.pp pname
|
||
8 years ago
|
end
|
||
|
|
||
8 years ago
|
module TraceElem = struct
|
||
8 years ago
|
module Kind = Access
|
||
8 years ago
|
|
||
8 years ago
|
type t = {site: CallSite.t; kind: Kind.t} [@@deriving compare]
|
||
8 years ago
|
|
||
8 years ago
|
let is_write {kind} =
|
||
8 years ago
|
match kind with
|
||
|
| InterfaceCall _ | Read _ | ContainerRead _
|
||
|
-> false
|
||
|
| ContainerWrite _ | Write _
|
||
|
-> true
|
||
8 years ago
|
|
||
8 years ago
|
let is_container_write {kind} =
|
||
8 years ago
|
match kind with
|
||
|
| InterfaceCall _ | Read _ | Write _ | ContainerRead _
|
||
|
-> false
|
||
|
| ContainerWrite _
|
||
|
-> true
|
||
8 years ago
|
|
||
8 years ago
|
let call_site {site} = site
|
||
8 years ago
|
|
||
8 years ago
|
let kind {kind} = kind
|
||
8 years ago
|
|
||
8 years ago
|
let make ?indexes:_ kind site = {kind; site}
|
||
8 years ago
|
|
||
8 years ago
|
let with_callsite t site = {t with site}
|
||
8 years ago
|
|
||
8 years ago
|
let pp fmt {site; kind} = F.fprintf fmt "%a at %a" Access.pp kind CallSite.pp site
|
||
8 years ago
|
|
||
8 years ago
|
let map ~f {site; kind} = {site; kind= Access.map ~f kind}
|
||
|
|
||
8 years ago
|
module Set = PrettyPrintable.MakePPSet (struct
|
||
8 years ago
|
type nonrec t = t
|
||
|
|
||
|
let compare = compare
|
||
|
|
||
|
let pp = pp
|
||
|
end)
|
||
8 years ago
|
end
|
||
|
|
||
8 years ago
|
let make_container_access access_path pname ~is_write loc =
|
||
8 years ago
|
let site = CallSite.make Typ.Procname.empty_block loc in
|
||
8 years ago
|
let access =
|
||
|
if is_write then Access.ContainerWrite (access_path, pname)
|
||
|
else Access.ContainerRead (access_path, pname)
|
||
|
in
|
||
|
TraceElem.make access site
|
||
8 years ago
|
|
||
8 years ago
|
let make_field_access access_path ~is_write loc =
|
||
|
let site = CallSite.make Typ.Procname.empty_block loc in
|
||
|
TraceElem.make (Access.make_field_access access_path ~is_write) site
|
||
|
|
||
|
let make_unannotated_call_access pname loc =
|
||
8 years ago
|
let site = CallSite.make Typ.Procname.empty_block loc in
|
||
8 years ago
|
TraceElem.make (Access.InterfaceCall pname) site
|
||
8 years ago
|
|
||
8 years ago
|
(* 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.
|
||
|
*)
|
||
8 years ago
|
module LocksDomain = AbstractDomain.BooleanAnd
|
||
8 years ago
|
|
||
|
module ThreadsDomain = struct
|
||
8 years ago
|
type astate = NoThread | AnyThreadButSelf | AnyThread [@@deriving compare]
|
||
8 years ago
|
|
||
8 years ago
|
let empty = NoThread
|
||
8 years ago
|
|
||
8 years ago
|
(* NoThread < AnyThreadButSelf < Any *)
|
||
8 years ago
|
let ( <= ) ~lhs ~rhs =
|
||
|
match (lhs, rhs) with
|
||
8 years ago
|
| NoThread, _
|
||
8 years ago
|
-> true
|
||
8 years ago
|
| _, NoThread
|
||
8 years ago
|
-> false
|
||
8 years ago
|
| _, AnyThread
|
||
8 years ago
|
-> true
|
||
8 years ago
|
| AnyThread, _
|
||
8 years ago
|
-> false
|
||
|
| _
|
||
8 years ago
|
-> Int.equal 0 (compare_astate lhs rhs)
|
||
8 years ago
|
|
||
|
let join astate1 astate2 =
|
||
|
match (astate1, astate2) with
|
||
8 years ago
|
| NoThread, astate | astate, NoThread
|
||
8 years ago
|
-> astate
|
||
8 years ago
|
| AnyThread, _ | _, AnyThread
|
||
|
-> AnyThread
|
||
|
| AnyThreadButSelf, AnyThreadButSelf
|
||
|
-> AnyThreadButSelf
|
||
8 years ago
|
|
||
|
let widen ~prev ~next ~num_iters:_ = join prev next
|
||
|
|
||
|
let pp fmt astate =
|
||
|
F.fprintf fmt
|
||
|
( match astate with
|
||
8 years ago
|
| NoThread
|
||
|
-> "NoThread"
|
||
|
| AnyThreadButSelf
|
||
|
-> "AnyThreadButSelf"
|
||
|
| AnyThread
|
||
|
-> "AnyThread" )
|
||
8 years ago
|
|
||
8 years ago
|
let is_empty = function NoThread -> true | _ -> false
|
||
8 years ago
|
|
||
8 years ago
|
let is_any_but_self = function AnyThreadButSelf -> true | _ -> false
|
||
8 years ago
|
|
||
8 years ago
|
let is_any = function AnyThread -> true | _ -> false
|
||
8 years ago
|
end
|
||
|
|
||
8 years ago
|
module PathDomain = SinkTrace.Make (TraceElem)
|
||
8 years ago
|
|
||
8 years ago
|
module Choice = struct
|
||
8 years ago
|
type t = OnMainThread | LockHeld [@@deriving compare]
|
||
8 years ago
|
|
||
|
let pp fmt = function
|
||
8 years ago
|
| OnMainThread
|
||
|
-> F.fprintf fmt "OnMainThread"
|
||
|
| LockHeld
|
||
|
-> F.fprintf fmt "LockHeld"
|
||
8 years ago
|
end
|
||
|
|
||
8 years ago
|
module Attribute = struct
|
||
8 years ago
|
type t = Functional | Choice of Choice.t [@@deriving compare]
|
||
8 years ago
|
|
||
|
let pp fmt = function
|
||
8 years ago
|
| Functional
|
||
|
-> F.fprintf fmt "Functional"
|
||
|
| Choice choice
|
||
|
-> Choice.pp fmt choice
|
||
8 years ago
|
|
||
8 years ago
|
module Set = PrettyPrintable.MakePPSet (struct
|
||
|
type nonrec t = t
|
||
|
|
||
|
let compare = compare
|
||
|
|
||
|
let pp = pp
|
||
|
end)
|
||
8 years ago
|
end
|
||
|
|
||
8 years ago
|
module AttributeSetDomain = AbstractDomain.InvertedSet (Attribute)
|
||
8 years ago
|
|
||
8 years ago
|
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
|
||
|
|
||
8 years ago
|
module AttributeMapDomain = struct
|
||
8 years ago
|
include AbstractDomain.InvertedMap (AccessPath) (AttributeSetDomain)
|
||
8 years ago
|
|
||
8 years ago
|
let add access_path attribute_set t =
|
||
|
if AttributeSetDomain.is_empty attribute_set then t else add access_path attribute_set t
|
||
|
|
||
8 years ago
|
let has_attribute access_path attribute t =
|
||
8 years ago
|
try find access_path t |> AttributeSetDomain.mem attribute
|
||
|
with Not_found -> false
|
||
8 years ago
|
|
||
8 years ago
|
let get_choices access_path t =
|
||
|
try
|
||
|
let attributes = find access_path t in
|
||
8 years ago
|
List.filter_map
|
||
|
~f:(function Attribute.Choice c -> Some c | _ -> None)
|
||
|
(AttributeSetDomain.elements attributes)
|
||
|
with Not_found -> []
|
||
8 years ago
|
|
||
8 years ago
|
let add_attribute access_path attribute t =
|
||
|
let attribute_set =
|
||
8 years ago
|
( try find access_path t
|
||
|
with Not_found -> AttributeSetDomain.empty )
|
||
|
|> AttributeSetDomain.add attribute
|
||
|
in
|
||
8 years ago
|
add access_path attribute_set t
|
||
|
end
|
||
|
|
||
8 years ago
|
module Excluder = struct
|
||
8 years ago
|
type t = Thread | Lock | Both [@@deriving compare]
|
||
|
|
||
|
let pp fmt = function
|
||
8 years ago
|
| Thread
|
||
8 years ago
|
-> F.fprintf fmt "Thread"
|
||
8 years ago
|
| Lock
|
||
8 years ago
|
-> F.fprintf fmt "Lock"
|
||
8 years ago
|
| Both
|
||
8 years ago
|
-> F.fprintf fmt "both Thread and Lock"
|
||
8 years ago
|
end
|
||
|
|
||
8 years ago
|
module AccessPrecondition = struct
|
||
8 years ago
|
type t =
|
||
|
| Protected of Excluder.t
|
||
|
| Unprotected of IntSet.t
|
||
|
| TotallyUnprotected
|
||
|
[@@deriving compare]
|
||
8 years ago
|
|
||
|
let pp fmt = function
|
||
8 years ago
|
| Protected excl
|
||
|
-> F.fprintf fmt "ProtectedBy(%a)" Excluder.pp excl
|
||
8 years ago
|
| TotallyUnprotected
|
||
|
-> F.fprintf fmt "TotallyUnprotected"
|
||
|
| Unprotected indexes
|
||
|
-> F.fprintf fmt "Unprotected(%a)" (PrettyPrintable.pp_collection ~pp_item:Int.pp)
|
||
|
(IntSet.elements indexes)
|
||
8 years ago
|
|
||
|
let make 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
|
||
8 years ago
|
end
|
||
|
|
||
|
module AccessDomain = struct
|
||
8 years ago
|
include AbstractDomain.Map (AccessPrecondition) (PathDomain)
|
||
8 years ago
|
|
||
|
let add_access precondition access_path t =
|
||
|
let precondition_accesses =
|
||
|
try find precondition t
|
||
8 years ago
|
with Not_found -> PathDomain.empty
|
||
|
in
|
||
8 years ago
|
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
|
||
|
|
||
8 years ago
|
type astate =
|
||
8 years ago
|
{ threads: ThreadsDomain.astate
|
||
8 years ago
|
; locks: LocksDomain.astate
|
||
|
; accesses: AccessDomain.astate
|
||
8 years ago
|
; ownership: OwnershipDomain.astate
|
||
8 years ago
|
; attribute_map: AttributeMapDomain.astate }
|
||
8 years ago
|
|
||
8 years ago
|
let empty =
|
||
8 years ago
|
let threads = ThreadsDomain.empty in
|
||
8 years ago
|
let locks = false in
|
||
8 years ago
|
let accesses = AccessDomain.empty in
|
||
8 years ago
|
let ownership = OwnershipDomain.empty in
|
||
8 years ago
|
let attribute_map = AttributeMapDomain.empty in
|
||
8 years ago
|
{threads; locks; accesses; ownership; attribute_map}
|
||
8 years ago
|
|
||
8 years ago
|
let is_empty {threads; locks; accesses; ownership; attribute_map} =
|
||
8 years ago
|
ThreadsDomain.is_empty threads && not locks && AccessDomain.is_empty accesses
|
||
8 years ago
|
&& OwnershipDomain.is_empty ownership && AttributeMapDomain.is_empty attribute_map
|
||
|
|
||
8 years ago
|
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
|
||
8 years ago
|
|
||
|
let join astate1 astate2 =
|
||
8 years ago
|
if phys_equal astate1 astate2 then astate1
|
||
8 years ago
|
else
|
||
8 years ago
|
let threads = ThreadsDomain.join astate1.threads astate2.threads in
|
||
8 years ago
|
let locks = LocksDomain.join astate1.locks astate2.locks in
|
||
8 years ago
|
let accesses = AccessDomain.join astate1.accesses astate2.accesses in
|
||
8 years ago
|
let ownership = OwnershipDomain.join astate1.ownership astate2.ownership in
|
||
8 years ago
|
let attribute_map = AttributeMapDomain.join astate1.attribute_map astate2.attribute_map in
|
||
8 years ago
|
{threads; locks; accesses; ownership; attribute_map}
|
||
8 years ago
|
|
||
|
let widen ~prev ~next ~num_iters =
|
||
8 years ago
|
if phys_equal prev next then prev
|
||
8 years ago
|
else
|
||
8 years ago
|
let threads = ThreadsDomain.widen ~prev:prev.threads ~next:next.threads ~num_iters in
|
||
8 years ago
|
let locks = LocksDomain.widen ~prev:prev.locks ~next:next.locks ~num_iters in
|
||
8 years ago
|
let accesses = AccessDomain.widen ~prev:prev.accesses ~next:next.accesses ~num_iters in
|
||
8 years ago
|
let ownership = OwnershipDomain.widen ~prev:prev.ownership ~next:next.ownership ~num_iters in
|
||
8 years ago
|
let attribute_map =
|
||
8 years ago
|
AttributeMapDomain.widen ~prev:prev.attribute_map ~next:next.attribute_map ~num_iters
|
||
|
in
|
||
8 years ago
|
{threads; locks; accesses; ownership; attribute_map}
|
||
8 years ago
|
|
||
8 years ago
|
type summary =
|
||
8 years ago
|
{ threads: ThreadsDomain.astate
|
||
8 years ago
|
; locks: LocksDomain.astate
|
||
|
; accesses: AccessDomain.astate
|
||
|
; return_ownership: OwnershipAbstractValue.astate
|
||
8 years ago
|
; return_attributes: AttributeSetDomain.astate }
|
||
8 years ago
|
|
||
8 years ago
|
let pp_summary fmt {threads; locks; accesses; return_ownership; return_attributes} =
|
||
8 years ago
|
F.fprintf fmt
|
||
8 years ago
|
"@\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
|