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.
585 lines
19 KiB
585 lines
19 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 AccessExpression = HilExp.AccessExpression
|
|
module F = Format
|
|
module MF = MarkupFormatter
|
|
|
|
let pp_exp fmt exp =
|
|
match !Language.curr_language with
|
|
| Clang ->
|
|
AccessExpression.pp fmt exp
|
|
| Java ->
|
|
AccessPath.pp fmt (AccessExpression.to_access_path exp)
|
|
|
|
|
|
let rec should_keep_exp formals (exp : AccessExpression.t) =
|
|
match exp with
|
|
| FieldOffset (prefix, fld) ->
|
|
(not (String.is_substring ~substring:"$SwitchMap" (Fieldname.get_field_name fld)))
|
|
&& should_keep_exp formals prefix
|
|
| ArrayOffset (prefix, _, _) | AddressOf prefix | Dereference prefix ->
|
|
should_keep_exp formals prefix
|
|
| Base (LogicalVar _, _) ->
|
|
false
|
|
| Base (((ProgramVar pvar as var), _) as base) ->
|
|
Var.appears_in_source_code var
|
|
&& (not (Pvar.is_static_local pvar))
|
|
&& (Pvar.is_global pvar || FormalMap.is_formal base formals)
|
|
|
|
|
|
module Access = struct
|
|
type t =
|
|
| Read of {exp: AccessExpression.t}
|
|
| Write of {exp: AccessExpression.t}
|
|
| ContainerRead of {exp: AccessExpression.t; pname: Procname.t}
|
|
| ContainerWrite of {exp: AccessExpression.t; pname: Procname.t}
|
|
| InterfaceCall of {exp: AccessExpression.t; pname: Procname.t}
|
|
[@@deriving compare]
|
|
|
|
let make_field_access exp ~is_write = if is_write then Write {exp} else Read {exp}
|
|
|
|
let make_container_access exp pname ~is_write =
|
|
if is_write then ContainerWrite {exp; pname} else ContainerRead {exp; pname}
|
|
|
|
|
|
let make_unannotated_call_access exp pname = InterfaceCall {exp; pname}
|
|
|
|
let is_write = function
|
|
| InterfaceCall _ | Read _ | ContainerRead _ ->
|
|
false
|
|
| ContainerWrite _ | Write _ ->
|
|
true
|
|
|
|
|
|
let is_container_write = function
|
|
| InterfaceCall _ | Read _ | Write _ | ContainerRead _ ->
|
|
false
|
|
| ContainerWrite _ ->
|
|
true
|
|
|
|
|
|
let get_access_exp = function
|
|
| Read {exp} | Write {exp} | ContainerWrite {exp} | ContainerRead {exp} | InterfaceCall {exp} ->
|
|
exp
|
|
|
|
|
|
let should_keep formals access = get_access_exp access |> should_keep_exp formals
|
|
|
|
let map ~f access =
|
|
match access with
|
|
| Read {exp} ->
|
|
let exp' = f exp in
|
|
if phys_equal exp exp' then access else Read {exp= exp'}
|
|
| Write {exp} ->
|
|
let exp' = f exp in
|
|
if phys_equal exp exp' then access else Write {exp= exp'}
|
|
| ContainerWrite ({exp} as record) ->
|
|
let exp' = f exp in
|
|
if phys_equal exp exp' then access else ContainerWrite {record with exp= exp'}
|
|
| ContainerRead ({exp} as record) ->
|
|
let exp' = f exp in
|
|
if phys_equal exp exp' then access else ContainerRead {record with exp= exp'}
|
|
| InterfaceCall ({exp} as record) ->
|
|
let exp' = f exp in
|
|
if phys_equal exp exp' then access else InterfaceCall {record with exp= exp'}
|
|
|
|
|
|
let pp fmt = function
|
|
| Read {exp} ->
|
|
F.fprintf fmt "Read of %a" AccessExpression.pp exp
|
|
| Write {exp} ->
|
|
F.fprintf fmt "Write to %a" AccessExpression.pp exp
|
|
| ContainerRead {exp; pname} ->
|
|
F.fprintf fmt "Read of container %a via %a" AccessExpression.pp exp Procname.pp pname
|
|
| ContainerWrite {exp; pname} ->
|
|
F.fprintf fmt "Write to container %a via %a" AccessExpression.pp exp Procname.pp pname
|
|
| InterfaceCall {exp; pname} ->
|
|
F.fprintf fmt "Call to un-annotated interface method %a.%a" AccessExpression.pp exp
|
|
Procname.pp pname
|
|
|
|
|
|
let mono_lang_pp = MF.wrap_monospaced pp_exp
|
|
|
|
let describe fmt = function
|
|
| Read {exp} | Write {exp} ->
|
|
F.fprintf fmt "access to %a" mono_lang_pp exp
|
|
| ContainerRead {exp; pname} ->
|
|
F.fprintf fmt "Read of container %a via call to %s" mono_lang_pp exp
|
|
(MF.monospaced_to_string (Procname.get_method pname))
|
|
| ContainerWrite {exp; pname} ->
|
|
F.fprintf fmt "Write to container %a via call to %s" mono_lang_pp exp
|
|
(MF.monospaced_to_string (Procname.get_method pname))
|
|
| InterfaceCall {pname} ->
|
|
F.fprintf fmt "Call to un-annotated interface method %a" Procname.pp pname
|
|
end
|
|
|
|
module CallPrinter = struct
|
|
type t = CallSite.t
|
|
|
|
let pp fmt cs = F.fprintf fmt "call to %a" Procname.pp (CallSite.pname cs)
|
|
end
|
|
|
|
module LockDomain = struct
|
|
include AbstractDomain.CountDomain (struct
|
|
(** arbitrary threshold for max locks we expect to be held simultaneously *)
|
|
let max = 5
|
|
end)
|
|
|
|
let acquire_lock = increment
|
|
|
|
let release_lock = decrement
|
|
|
|
let integrate_summary ~caller_astate ~callee_astate = add caller_astate callee_astate
|
|
|
|
let is_locked t = not (is_bottom t)
|
|
end
|
|
|
|
module ThreadsDomain = struct
|
|
type t = NoThread | AnyThreadButSelf | AnyThread [@@deriving compare]
|
|
|
|
let bottom = NoThread
|
|
|
|
(* NoThread < AnyThreadButSelf < Any *)
|
|
let leq ~lhs ~rhs =
|
|
phys_equal lhs rhs
|
|
||
|
|
match (lhs, rhs) with
|
|
| NoThread, _ ->
|
|
true
|
|
| _, NoThread ->
|
|
false
|
|
| _, AnyThread ->
|
|
true
|
|
| AnyThread, _ ->
|
|
false
|
|
| _ ->
|
|
Int.equal 0 (compare lhs rhs)
|
|
|
|
|
|
let join astate1 astate2 =
|
|
if phys_equal astate1 astate2 then astate1
|
|
else
|
|
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.pp_print_string fmt
|
|
( match astate with
|
|
| NoThread ->
|
|
"NoThread"
|
|
| AnyThreadButSelf ->
|
|
"AnyThreadButSelf"
|
|
| AnyThread ->
|
|
"AnyThread" )
|
|
|
|
|
|
(* at least one access is known to occur on a background thread *)
|
|
let can_conflict astate1 astate2 =
|
|
match join astate1 astate2 with AnyThread -> true | NoThread | AnyThreadButSelf -> false
|
|
|
|
|
|
let is_bottom = function NoThread -> true | _ -> false
|
|
|
|
let is_any_but_self = function AnyThreadButSelf -> true | _ -> false
|
|
|
|
let is_any = function AnyThread -> true | _ -> false
|
|
|
|
let integrate_summary ~caller_astate ~callee_astate =
|
|
(* if we know the callee runs on the main thread, assume the caller does too. otherwise, keep
|
|
the caller's thread context *)
|
|
match callee_astate with AnyThreadButSelf -> callee_astate | _ -> caller_astate
|
|
end
|
|
|
|
module OwnershipAbstractValue = struct
|
|
type t = OwnedIf of IntSet.t | Unowned [@@deriving compare]
|
|
|
|
let owned = OwnedIf IntSet.empty
|
|
|
|
let is_owned = function OwnedIf set -> IntSet.is_empty set | Unowned -> false
|
|
|
|
let unowned = Unowned
|
|
|
|
let make_owned_if formal_index = OwnedIf (IntSet.singleton formal_index)
|
|
|
|
let leq ~lhs ~rhs =
|
|
phys_equal lhs rhs
|
|
||
|
|
match (lhs, rhs) with
|
|
| _, Unowned ->
|
|
true (* Unowned is top *)
|
|
| Unowned, _ ->
|
|
false
|
|
| OwnedIf s1, OwnedIf s2 ->
|
|
IntSet.subset s1 s2
|
|
|
|
|
|
let join astate1 astate2 =
|
|
if phys_equal astate1 astate2 then astate1
|
|
else
|
|
match (astate1, astate2) with
|
|
| _, Unowned | Unowned, _ ->
|
|
Unowned
|
|
| OwnedIf s1, OwnedIf s2 ->
|
|
OwnedIf (IntSet.union s1 s2)
|
|
|
|
|
|
let widen ~prev ~next ~num_iters:_ = join prev next
|
|
|
|
let pp fmt = function
|
|
| Unowned ->
|
|
F.pp_print_string fmt "Unowned"
|
|
| OwnedIf s ->
|
|
if IntSet.is_empty s then F.pp_print_string fmt "Owned"
|
|
else
|
|
F.fprintf fmt "OwnedIf%a"
|
|
(PrettyPrintable.pp_collection ~pp_item:Int.pp)
|
|
(IntSet.elements s)
|
|
end
|
|
|
|
module AccessSnapshot = struct
|
|
module AccessSnapshotElem = struct
|
|
type t =
|
|
{ access: Access.t
|
|
; thread: ThreadsDomain.t
|
|
; lock: bool
|
|
; ownership_precondition: OwnershipAbstractValue.t }
|
|
[@@deriving compare]
|
|
|
|
let pp fmt {access; thread; lock; ownership_precondition} =
|
|
F.fprintf fmt "Access: %a Thread: %a Lock: %b Pre: %a" Access.pp access ThreadsDomain.pp
|
|
thread lock OwnershipAbstractValue.pp ownership_precondition
|
|
|
|
|
|
let describe fmt {access} = Access.describe fmt access
|
|
end
|
|
|
|
include ExplicitTrace.MakeTraceElemModuloLocation (AccessSnapshotElem) (CallPrinter)
|
|
|
|
let is_write {elem= {access}} = Access.is_write access
|
|
|
|
let is_container_write {elem= {access}} = Access.is_container_write access
|
|
|
|
let filter formals t =
|
|
if
|
|
(not (OwnershipAbstractValue.is_owned t.elem.ownership_precondition))
|
|
&& Access.should_keep formals t.elem.access
|
|
then Some t
|
|
else None
|
|
|
|
|
|
let make_if_not_owned formals access lock thread ownership_precondition loc =
|
|
make {access; lock; thread; ownership_precondition} loc |> filter formals
|
|
|
|
|
|
let make_unannotated_call_access formals exp pname lock ownership loc =
|
|
let lock = LockDomain.is_locked lock in
|
|
let access = Access.make_unannotated_call_access exp pname in
|
|
make_if_not_owned formals access lock ownership loc
|
|
|
|
|
|
let make_access formals acc_exp ~is_write loc lock thread ownership_precondition =
|
|
let lock = LockDomain.is_locked lock in
|
|
let access = Access.make_field_access acc_exp ~is_write in
|
|
make_if_not_owned formals access lock thread ownership_precondition loc
|
|
|
|
|
|
let make_container_access formals acc_exp ~is_write callee loc lock thread ownership_precondition
|
|
=
|
|
let lock = LockDomain.is_locked lock in
|
|
let access = Access.make_container_access acc_exp callee ~is_write in
|
|
make_if_not_owned formals access lock thread ownership_precondition loc
|
|
|
|
|
|
let map_opt formals ~f t =
|
|
map t ~f:(fun elem -> {elem with access= Access.map ~f elem.access}) |> filter formals
|
|
|
|
|
|
let update_callee_access formals snapshot callsite ownership_precondition threads locks =
|
|
let thread =
|
|
ThreadsDomain.integrate_summary ~callee_astate:snapshot.elem.thread ~caller_astate:threads
|
|
in
|
|
let lock = snapshot.elem.lock || LockDomain.is_locked locks in
|
|
with_callsite snapshot callsite
|
|
|> map ~f:(fun elem -> {elem with lock; thread; ownership_precondition})
|
|
|> filter formals
|
|
|
|
|
|
let is_unprotected {elem= {thread; lock; ownership_precondition}} =
|
|
(not (ThreadsDomain.is_any_but_self thread))
|
|
&& (not lock)
|
|
&& not (OwnershipAbstractValue.is_owned ownership_precondition)
|
|
end
|
|
|
|
module AccessDomain = struct
|
|
include AbstractDomain.FiniteSet (AccessSnapshot)
|
|
|
|
let add_opt snapshot_opt astate =
|
|
Option.fold snapshot_opt ~init:astate ~f:(fun acc s -> add s acc)
|
|
end
|
|
|
|
module OwnershipDomain = struct
|
|
include AbstractDomain.Map (AccessExpression) (OwnershipAbstractValue)
|
|
|
|
(* return the first non-Unowned ownership value found when checking progressively shorter
|
|
prefixes of [access_exp] *)
|
|
let rec get_owned (access_exp : AccessExpression.t) astate =
|
|
match find_opt access_exp astate with
|
|
| Some (OwnershipAbstractValue.OwnedIf _ as v) ->
|
|
v
|
|
| _ -> (
|
|
match access_exp with
|
|
| Base _ ->
|
|
OwnershipAbstractValue.Unowned
|
|
| AddressOf prefix | Dereference prefix | FieldOffset (prefix, _) | ArrayOffset (prefix, _, _)
|
|
->
|
|
get_owned prefix astate )
|
|
|
|
|
|
let rec ownership_of_expr expr ownership =
|
|
let open HilExp in
|
|
match expr with
|
|
| AccessExpression access_expr ->
|
|
get_owned access_expr ownership
|
|
| Constant _ ->
|
|
OwnershipAbstractValue.owned
|
|
| Exception e (* treat exceptions as transparent wrt ownership *) | Cast (_, e) ->
|
|
ownership_of_expr e ownership
|
|
| _ ->
|
|
OwnershipAbstractValue.unowned
|
|
|
|
|
|
let propagate_assignment lhs_access_exp rhs_exp ownership =
|
|
if AccessExpression.get_base lhs_access_exp |> fst |> Var.is_global then
|
|
(* do not assign ownership to access expressions rooted at globals *)
|
|
ownership
|
|
else
|
|
let rhs_ownership_value = ownership_of_expr rhs_exp ownership in
|
|
add lhs_access_exp rhs_ownership_value ownership
|
|
|
|
|
|
let propagate_return ret_access_exp return_ownership actuals ownership =
|
|
let get_ownership formal_index init =
|
|
List.nth actuals formal_index
|
|
(* simply skip formal if we cannot find its actual, as opposed to assuming non-ownership *)
|
|
|> Option.fold ~init ~f:(fun acc expr ->
|
|
OwnershipAbstractValue.join acc (ownership_of_expr expr ownership) )
|
|
in
|
|
let ret_ownership_wrt_actuals =
|
|
match return_ownership with
|
|
| OwnershipAbstractValue.Unowned ->
|
|
return_ownership
|
|
| OwnershipAbstractValue.OwnedIf formal_indexes ->
|
|
IntSet.fold get_ownership formal_indexes OwnershipAbstractValue.owned
|
|
in
|
|
add ret_access_exp ret_ownership_wrt_actuals ownership
|
|
end
|
|
|
|
module Attribute = struct
|
|
type t = Nothing | Functional | OnMainThread | LockHeld | Synchronized [@@deriving equal]
|
|
|
|
let pp fmt t =
|
|
( match t with
|
|
| Nothing ->
|
|
"Nothing"
|
|
| Functional ->
|
|
"Functional"
|
|
| OnMainThread ->
|
|
"OnMainThread"
|
|
| LockHeld ->
|
|
"LockHeld"
|
|
| Synchronized ->
|
|
"Synchronized" )
|
|
|> F.pp_print_string fmt
|
|
|
|
|
|
let top = Nothing
|
|
|
|
let is_top = function Nothing -> true | _ -> false
|
|
|
|
let join t t' = if equal t t' then t else Nothing
|
|
|
|
let leq ~lhs ~rhs = equal (join lhs rhs) rhs
|
|
|
|
let widen ~prev ~next ~num_iters:_ = join prev next
|
|
end
|
|
|
|
module AttributeMapDomain = struct
|
|
include AbstractDomain.SafeInvertedMap (AccessExpression) (Attribute)
|
|
|
|
let get acc_exp t = find_opt acc_exp t |> Option.value ~default:Attribute.top
|
|
|
|
let is_functional t access_expression =
|
|
match find_opt access_expression t with Some Functional -> true | _ -> false
|
|
|
|
|
|
let is_synchronized t access_expression =
|
|
match find_opt access_expression t with Some Synchronized -> true | _ -> false
|
|
|
|
|
|
let rec attribute_of_expr attribute_map (e : HilExp.t) =
|
|
match e with
|
|
| AccessExpression access_expr ->
|
|
get access_expr attribute_map
|
|
| Constant _ ->
|
|
Attribute.Functional
|
|
| Exception expr (* treat exceptions as transparent wrt attributes *) | Cast (_, expr) ->
|
|
attribute_of_expr attribute_map expr
|
|
| UnaryOperator (_, expr, _) ->
|
|
attribute_of_expr attribute_map expr
|
|
| BinaryOperator (_, expr1, expr2) ->
|
|
let attribute1 = attribute_of_expr attribute_map expr1 in
|
|
let attribute2 = attribute_of_expr attribute_map expr2 in
|
|
Attribute.join attribute1 attribute2
|
|
| Closure _ | Sizeof _ ->
|
|
Attribute.top
|
|
|
|
|
|
let propagate_assignment lhs_access_expression rhs_exp attribute_map =
|
|
let rhs_attribute = attribute_of_expr attribute_map rhs_exp in
|
|
add lhs_access_expression rhs_attribute attribute_map
|
|
end
|
|
|
|
type t =
|
|
{ threads: ThreadsDomain.t
|
|
; locks: LockDomain.t
|
|
; accesses: AccessDomain.t
|
|
; ownership: OwnershipDomain.t
|
|
; attribute_map: AttributeMapDomain.t }
|
|
|
|
let bottom =
|
|
let threads = ThreadsDomain.bottom in
|
|
let locks = LockDomain.bottom 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_bottom {threads; locks; accesses; ownership; attribute_map} =
|
|
ThreadsDomain.is_bottom threads && LockDomain.is_bottom locks && AccessDomain.is_empty accesses
|
|
&& OwnershipDomain.is_empty ownership
|
|
&& AttributeMapDomain.is_empty attribute_map
|
|
|
|
|
|
let leq ~lhs ~rhs =
|
|
if phys_equal lhs rhs then true
|
|
else
|
|
ThreadsDomain.leq ~lhs:lhs.threads ~rhs:rhs.threads
|
|
&& LockDomain.leq ~lhs:lhs.locks ~rhs:rhs.locks
|
|
&& AccessDomain.leq ~lhs:lhs.accesses ~rhs:rhs.accesses
|
|
&& AttributeMapDomain.leq ~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 = LockDomain.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 = LockDomain.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.t
|
|
; locks: LockDomain.t
|
|
; accesses: AccessDomain.t
|
|
; return_ownership: OwnershipAbstractValue.t
|
|
; return_attribute: Attribute.t
|
|
; attributes: AttributeMapDomain.t }
|
|
|
|
let empty_summary =
|
|
{ threads= ThreadsDomain.bottom
|
|
; locks= LockDomain.bottom
|
|
; accesses= AccessDomain.bottom
|
|
; return_ownership= OwnershipAbstractValue.unowned
|
|
; return_attribute= Attribute.top
|
|
; attributes= AttributeMapDomain.top }
|
|
|
|
|
|
let pp_summary fmt {threads; locks; accesses; return_ownership; return_attribute; attributes} =
|
|
F.fprintf fmt
|
|
"@\n\
|
|
Threads: %a, Locks: %a @\n\
|
|
Accesses %a @\n\
|
|
Ownership: %a @\n\
|
|
Return Attribute: %a @\n\
|
|
Attributes: %a @\n"
|
|
ThreadsDomain.pp threads LockDomain.pp locks AccessDomain.pp accesses OwnershipAbstractValue.pp
|
|
return_ownership Attribute.pp return_attribute AttributeMapDomain.pp attributes
|
|
|
|
|
|
let pp fmt {threads; locks; accesses; ownership; attribute_map} =
|
|
F.fprintf fmt "Threads: %a, Locks: %a @\nAccesses %a @\nOwnership: %a @\nAttributes: %a @\n"
|
|
ThreadsDomain.pp threads LockDomain.pp locks AccessDomain.pp accesses OwnershipDomain.pp
|
|
ownership AttributeMapDomain.pp attribute_map
|
|
|
|
|
|
let add_unannotated_call_access formals pname actuals loc (astate : t) =
|
|
match actuals with
|
|
| [] ->
|
|
astate
|
|
| receiver_hilexp :: _ -> (
|
|
match HilExp.get_access_exprs receiver_hilexp with
|
|
| [] | _ :: _ :: _ ->
|
|
(* if no access exps involved, or if more than one (should be impossible), ignore *)
|
|
astate
|
|
| [receiver] ->
|
|
let snapshot =
|
|
AccessSnapshot.make_unannotated_call_access formals receiver pname astate.locks
|
|
astate.threads Unowned loc
|
|
in
|
|
{astate with accesses= AccessDomain.add_opt snapshot astate.accesses} )
|
|
|
|
|
|
let astate_to_summary proc_desc formals {threads; locks; accesses; ownership; attribute_map} =
|
|
let proc_name = Procdesc.get_proc_name proc_desc in
|
|
let return_var_exp =
|
|
AccessExpression.base
|
|
(Var.of_pvar (Pvar.get_ret_pvar proc_name), Procdesc.get_ret_type proc_desc)
|
|
in
|
|
let return_ownership = OwnershipDomain.get_owned return_var_exp ownership in
|
|
let return_attribute = AttributeMapDomain.get return_var_exp attribute_map in
|
|
let locks =
|
|
(* if method is [synchronized] released the lock once. *)
|
|
if Procdesc.is_java_synchronized proc_desc then LockDomain.release_lock locks else locks
|
|
in
|
|
let attributes =
|
|
(* store only the [Synchronized] attribute for class initializers/constructors *)
|
|
if Procname.is_java_class_initializer proc_name || Procname.is_constructor proc_name then
|
|
AttributeMapDomain.filter
|
|
(fun exp attribute ->
|
|
match attribute with Synchronized -> should_keep_exp formals exp | _ -> false )
|
|
attribute_map
|
|
else AttributeMapDomain.top
|
|
in
|
|
{threads; locks; accesses; return_ownership; return_attribute; attributes}
|