[thread-safety] make a distinguished access kind for container writes

Summary:
The way we represented container writes before was pretty hacky: just use a dummy field for the name of the method that performs the container write.
This diff introduces a new access kind for container writes that is much more structured.
This will make it easier to soundly handle aliasing between containers and support container reads in the near future.

Reviewed By: da319

Differential Revision: D5465747

fbshipit-source-id: e021ec2
master
Sam Blackshear 7 years ago committed by Facebook Github Bot
parent f49d292c3b
commit 2dcde3a812

@ -25,35 +25,6 @@ let is_owned access_path attribute_map =
ThreadSafetyDomain.AttributeMapDomain.has_attribute access_path
ThreadSafetyDomain.Attribute.unconditionally_owned attribute_map
let container_write_string = "infer.dummy.__CONTAINERWRITE__"
(* return (name of container, name of mutating function call) pair *)
let get_container_write_desc sink =
match ThreadSafetyDomain.TraceElem.kind sink with
| Write ((base_var, _), access_list)
-> (
let get_container_write_desc_ call_name container_name =
match
String.chop_prefix (Typ.Fieldname.to_string call_name) ~prefix:container_write_string
with
| Some call_name
-> Some (container_name, call_name)
| None
-> None
in
match List.rev access_list with
| (FieldAccess call_name) :: (FieldAccess container_name) :: _
-> get_container_write_desc_ call_name (Typ.Fieldname.to_string container_name)
| [(FieldAccess call_name)]
-> get_container_write_desc_ call_name (F.asprintf "%a" Var.pp base_var)
| _
-> None )
| Read _ | InterfaceCall _
-> (* TODO: support Read *)
None
let is_container_write_sink sink = Option.is_some (get_container_write_desc sink)
(*Bit of redundancy with code in is_unprotected, might alter later *)
let make_excluder locks threads =
if locks && not threads then ThreadSafetyDomain.Excluder.Lock
@ -497,15 +468,9 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
let callee_accesses =
if is_synchronized_container callee_pname receiver_ap tenv then AccessDomain.empty
else
let dummy_fieldname =
Typ.Fieldname.Java.from_string
(container_write_string ^ Typ.Procname.get_method callee_pname)
in
let dummy_access_ap =
(fst receiver_ap, snd receiver_ap @ [AccessPath.FieldAccess dummy_fieldname])
in
AccessDomain.add_access (Unprotected (Some 0))
(make_field_access dummy_access_ap ~is_write:true callee_loc) AccessDomain.empty
let container_access = make_container_access receiver_ap callee_pname in
AccessDomain.add_access (Unprotected (Some 0)) (container_access ~is_write:true callee_loc)
AccessDomain.empty
in
(* TODO: for now all formals escape *)
(* we need a more intelligent escape analysis, that branches on whether
@ -1036,12 +1001,17 @@ let analyze_procedure {Callbacks.proc_desc; tenv; summary} =
else Summary.update_summary empty_post summary
module AccessListMap = Caml.Map.Make (struct
type t = AccessPath.Raw.t option
type t = ThreadSafetyDomain.Access.t
(* TODO -- keep this compare to satisfy the order of tests, consider using Raw.compare *)
let compare =
Option.compare (fun access_path1 access_path2 ->
List.compare AccessPath.compare_access (snd access_path1) (snd access_path2) )
let compare access1 access2 =
let open ThreadSafetyDomain in
match (access1, access2) with
| ( (Access.Read access_path1 | Write access_path1)
, (Access.Read access_path2 | Write access_path2) )
-> List.compare AccessPath.compare_access (snd access_path1) (snd access_path2)
| _
-> Access.compare access1 access2
end)
let get_current_class_and_threadsafe_superclasses tenv pname =
@ -1086,33 +1056,33 @@ let get_all_accesses_with_pre pre_filter access_filter accesses =
let get_all_accesses = get_all_accesses_with_pre (fun _ -> true)
let pp_container_access fmt (container_name, function_name) =
F.fprintf fmt "container %s via call to %s" (MF.monospaced_to_string container_name)
(MF.monospaced_to_string function_name)
let pp_container_access fmt (access_path, access_pname) =
F.fprintf fmt "container %a via call to %s" (MF.wrap_monospaced AccessPath.Raw.pp) access_path
(MF.monospaced_to_string (Typ.Procname.get_method access_pname))
let pp_access fmt sink =
match get_container_write_desc sink with
| Some container_write_desc
-> pp_container_access fmt container_write_desc
| None ->
match ThreadSafetyDomain.PathDomain.Sink.kind sink with
| Read access_path | Write access_path
-> F.fprintf fmt "%a" (MF.wrap_monospaced AccessPath.pp_access_list) (snd access_path)
| ContainerWrite (access_path, access_pname)
-> pp_container_access fmt (access_path, access_pname)
| InterfaceCall _ as access
-> F.fprintf fmt "%a" ThreadSafetyDomain.Access.pp access
let desc_of_sink sink =
match get_container_write_desc sink with
| Some container_write_desc
-> F.asprintf "%a" pp_container_access container_write_desc
| None
-> let sink_pname = CallSite.pname (ThreadSafetyDomain.PathDomain.Sink.call_site sink) in
if Typ.Procname.equal sink_pname Typ.Procname.empty_block then
let sink_pname = CallSite.pname (ThreadSafetyDomain.PathDomain.Sink.call_site sink) in
match ThreadSafetyDomain.PathDomain.Sink.kind sink with
| Read _ | Write _
-> F.asprintf "access to %a" pp_access sink
| InterfaceCall interface_pname
-> F.asprintf "call to %a" Typ.Procname.pp interface_pname
-> if Typ.Procname.equal sink_pname Typ.Procname.empty_block then
F.asprintf "access to %a" pp_access sink
else F.asprintf "call to %a" Typ.Procname.pp sink_pname
| ContainerWrite (access_path, access_pname)
-> if Typ.Procname.equal sink_pname access_pname then
F.asprintf "Write to %a" pp_container_access (access_path, access_pname)
else F.asprintf "call to %a" Typ.Procname.pp sink_pname
| InterfaceCall _ as access
-> if Typ.Procname.equal sink_pname Typ.Procname.empty_block then
F.asprintf "%a1" ThreadSafetyDomain.Access.pp access
else F.asprintf "call to %a" Typ.Procname.pp sink_pname
let trace_of_pname orig_sink orig_pdesc callee_pname =
@ -1196,7 +1166,8 @@ let make_unprotected_write_description tenv pname final_sink_site initial_sink_s
Format.asprintf "Unprotected write. Non-private method %a%s %s %a outside of synchronization.%s"
(MF.wrap_monospaced pp_procname_short) pname
(if CallSite.equal final_sink_site initial_sink_site then "" else " indirectly")
(if is_container_write_sink final_sink then "mutates" else "writes to field")
( if ThreadSafetyDomain.TraceElem.is_container_write final_sink then "mutates"
else "writes to field" )
pp_access final_sink (calculate_addendum_message tenv pname)
let make_read_write_race_description conflicts tenv pname final_sink_site initial_sink_site
@ -1282,7 +1253,7 @@ let report_unsafe_accesses aggregated_access_map =
CallSite.Set.mem (TraceElem.call_site access) reported_sites
||
match TraceElem.kind access with
| Access.Write _
| Access.Write _ | Access.ContainerWrite _
-> Typ.Procname.Set.mem pname reported_writes
| Access.Read _
-> Typ.Procname.Set.mem pname reported_reads
@ -1292,7 +1263,7 @@ let report_unsafe_accesses aggregated_access_map =
let update_reported access pname reported =
let reported_sites = CallSite.Set.add (TraceElem.call_site access) reported.reported_sites in
match TraceElem.kind access with
| Access.Write _
| Access.Write _ | Access.ContainerWrite _
-> let reported_writes = Typ.Procname.Set.add pname reported.reported_writes in
{reported with reported_writes; reported_sites}
| Access.Read _
@ -1312,7 +1283,7 @@ let report_unsafe_accesses aggregated_access_map =
| Access.InterfaceCall _, AccessPrecondition.Protected _
-> (* un-annotated interface call, but it's protected by a lock/thread. don't report *)
reported_acc
| Access.Write _, AccessPrecondition.Unprotected _ -> (
| (Access.Write _ | ContainerWrite _), AccessPrecondition.Unprotected _ -> (
match Procdesc.get_proc_name pdesc with
| Java _
-> if threaded then reported_acc
@ -1324,7 +1295,7 @@ let report_unsafe_accesses aggregated_access_map =
| _
-> (* Do not report unprotected writes for ObjC_Cpp *)
reported_acc )
| Access.Write _, AccessPrecondition.Protected _
| (Access.Write _ | ContainerWrite _), AccessPrecondition.Protected _
-> (* protected write, do nothing *)
reported_acc
| Access.Read _, AccessPrecondition.Unprotected _
@ -1478,31 +1449,25 @@ let quotient_access_map acc_map =
let rec aux acc m =
if AccessListMap.is_empty m then acc
else
let k_opt, vals = AccessListMap.choose m in
let k, vals = AccessListMap.choose m in
let _, _, _, tenv, _ =
List.find_exn vals ~f:(fun (elem, _, _, _, _) ->
Option.equal
(fun e1 e2 -> AccessPath.Raw.equal e1 e2)
k_opt
(ThreadSafetyDomain.Access.get_access_path (ThreadSafetyDomain.TraceElem.kind elem))
)
ThreadSafetyDomain.Access.equal (ThreadSafetyDomain.TraceElem.kind elem) k )
in
(* assumption: the tenv for k is sufficient for k' too *)
let k_part, non_k_part =
AccessListMap.partition
(fun k_opt' _ ->
match (k_opt', k_opt) with
| Some k', Some k
-> may_alias tenv k k'
| None, None
-> true
(fun k' _ ->
match (k, k') with
| (Read ap1 | Write ap1), (Read ap2 | Write ap2)
-> may_alias tenv ap1 ap2
| _
-> false)
-> ThreadSafetyDomain.Access.equal k k')
m
in
if AccessListMap.is_empty k_part then failwith "may_alias is not reflexive!" ;
let k_accesses = AccessListMap.fold (fun _ v acc' -> List.append v acc') k_part [] in
let new_acc = AccessListMap.add k_opt k_accesses acc in
let new_acc = AccessListMap.add k k_accesses acc in
aux new_acc non_k_part
in
aux AccessListMap.empty acc_map
@ -1510,14 +1475,18 @@ let quotient_access_map acc_map =
(* decide if we should throw away a path before doing safety analysis
for now, just check for whether the access is within a switch-map
that is auto-generated by Java. *)
let should_filter_access (_, path) =
let check_access_step = function
let should_filter_access access =
match ThreadSafetyDomain.Access.get_access_path access with
| Some (_, path)
-> let check_access_step = function
| AccessPath.ArrayAccess _
-> false
| AccessPath.FieldAccess fld
-> String.is_substring ~substring:"$SwitchMap" (Typ.Fieldname.to_string fld)
in
List.exists path ~f:check_access_step
| None
-> false
(* create a map from [abstraction of a memory loc] -> accesses that may touch that memory loc. for
now, our abstraction is an access path like x.f.g whose concretization is the set of memory cells
@ -1529,14 +1498,14 @@ let make_results_table file_env =
(fun pre accesses acc ->
PathDomain.Sinks.fold
(fun access acc ->
let access_path_opt = Access.get_access_path (TraceElem.kind access) in
if Option.exists ~f:should_filter_access access_path_opt then acc
let access_kind = TraceElem.kind access in
if should_filter_access access_kind then acc
else
let grouped_accesses =
try AccessListMap.find access_path_opt acc
try AccessListMap.find access_kind acc
with Not_found -> []
in
AccessListMap.add access_path_opt
AccessListMap.add access_kind
((access, pre, threaded, tenv, pdesc) :: grouped_accesses) acc)
(PathDomain.sinks accesses) acc)
accesses acc

@ -14,6 +14,7 @@ module Access = struct
type t =
| Read of AccessPath.Raw.t
| Write of AccessPath.Raw.t
| ContainerWrite of AccessPath.Raw.t * Typ.Procname.t
| InterfaceCall of Typ.Procname.t
[@@deriving compare]
@ -21,16 +22,21 @@ module Access = struct
if is_write then Write access_path else Read access_path
let get_access_path = function
| Read access_path | Write access_path
| Read access_path | Write access_path | ContainerWrite (access_path, _)
-> Some access_path
| InterfaceCall _
-> None
let equal t1 t2 = Int.equal (compare t1 t2) 0
let pp fmt = function
| Read access_path
-> F.fprintf fmt "Read of %a" AccessPath.Raw.pp access_path
| Write access_path
-> F.fprintf fmt "Write to %a" AccessPath.Raw.pp access_path
| ContainerWrite (access_path, pname)
-> F.fprintf fmt "Write to container %a via %a" AccessPath.Raw.pp access_path Typ.Procname.pp
pname
| InterfaceCall pname
-> F.fprintf fmt "Call to un-annotated interface method %a" Typ.Procname.pp pname
end
@ -40,12 +46,11 @@ module TraceElem = struct
type t = {site: CallSite.t; kind: Kind.t} [@@deriving compare]
let is_read {kind} = match kind with Read _ -> true | InterfaceCall _ | Write _ -> false
let is_write {kind} = match kind with InterfaceCall _ | Read _ -> false | Write _ -> true
let is_write {kind} =
match kind with InterfaceCall _ | Read _ -> false | ContainerWrite _ | Write _ -> true
let is_interface_call {kind} =
match kind with InterfaceCall _ -> true | Read _ | Write _ -> false
let is_container_write {kind} =
match kind with InterfaceCall _ | Read _ | Write _ -> false | ContainerWrite _ -> true
let call_site {site} = site
@ -66,6 +71,10 @@ module TraceElem = struct
end)
end
let make_container_access access_path pname ~is_write:_ loc =
let site = CallSite.make Typ.Procname.empty_block loc in
TraceElem.make (Access.ContainerWrite (access_path, pname)) site
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

@ -12,14 +12,17 @@ module F = Format
module Access : sig
type t =
| Read of AccessPath.Raw.t (** Field read *)
| Write of AccessPath.Raw.t (** Field write *)
| Read of AccessPath.Raw.t (** Field or array read *)
| Write of AccessPath.Raw.t (** Field or array write *)
| ContainerWrite of AccessPath.Raw.t * Typ.Procname.t (** Write to container object *)
| InterfaceCall of Typ.Procname.t
(** Call to method of interface not annotated with @ThreadSafe *)
[@@deriving compare]
val get_access_path : t -> AccessPath.Raw.t option
val equal : t -> t -> bool
val pp : F.formatter -> t -> unit
end
@ -28,9 +31,7 @@ module TraceElem : sig
val is_write : t -> bool
val is_read : t -> bool
val is_interface_call : t -> bool
val is_container_write : t -> bool
end
(** A bool that is true if a lock is definitely held. Note that this is unsound because it assumes
@ -178,6 +179,9 @@ type summary =
include AbstractDomain.WithBottom with type astate := astate
val make_container_access :
AccessPath.Raw.t -> Typ.Procname.t -> is_write:bool -> Location.t -> TraceElem.t
val make_field_access : AccessPath.Raw.t -> is_write:bool -> Location.t -> TraceElem.t
val make_unannotated_call_access : Typ.Procname.t -> Location.t -> TraceElem.t

@ -21,24 +21,24 @@ codetoanalyze/java/threadsafety/Builders.java, void TopLevelBuilder.setG(String)
codetoanalyze/java/threadsafety/Constructors.java, Constructors Constructors.singletonBad(), 2, THREAD_SAFETY_VIOLATION, [call to Constructors.<init>(Object),access to `Constructors.staticField`]
codetoanalyze/java/threadsafety/Constructors.java, Constructors.<init>(), 1, THREAD_SAFETY_VIOLATION, [access to `Constructors.staticField`]
codetoanalyze/java/threadsafety/Constructors.java, Constructors.<init>(Constructors), 1, THREAD_SAFETY_VIOLATION, [access to `Constructors.field`]
codetoanalyze/java/threadsafety/Containers.java, void Containers.addToSimpleArrayMapBad(SimpleArrayMap), 1, THREAD_SAFETY_VIOLATION, [container `&map` via call to `put`]
codetoanalyze/java/threadsafety/Containers.java, void Containers.addToSparseArrayBad(SparseArray), 1, THREAD_SAFETY_VIOLATION, [container `&sparseArray` via call to `put`]
codetoanalyze/java/threadsafety/Containers.java, void Containers.addToSparseArrayCompatBad(SparseArrayCompat), 1, THREAD_SAFETY_VIOLATION, [container `&sparseArray` via call to `put`]
codetoanalyze/java/threadsafety/Containers.java, void Containers.containerWrapperUnownedWriteBad(Object), 1, THREAD_SAFETY_VIOLATION, [container `codetoanalyze.java.checkers.ContainerWrapper.children` via call to `add`,container `codetoanalyze.java.checkers.ContainerWrapper.children` via call to `add`,container `codetoanalyze.java.checkers.ContainerWrapper.children` via call to `add`]
codetoanalyze/java/threadsafety/Containers.java, void Containers.listAddAllBad(Collection), 1, THREAD_SAFETY_VIOLATION, [container `codetoanalyze.java.checkers.Containers.mList` via call to `addAll`]
codetoanalyze/java/threadsafety/Containers.java, void Containers.listAddBad1(String), 1, THREAD_SAFETY_VIOLATION, [container `codetoanalyze.java.checkers.Containers.mList` via call to `add`]
codetoanalyze/java/threadsafety/Containers.java, void Containers.listAddBad2(int,String), 1, THREAD_SAFETY_VIOLATION, [container `codetoanalyze.java.checkers.Containers.mList` via call to `add`]
codetoanalyze/java/threadsafety/Containers.java, void Containers.listClearBad(), 1, THREAD_SAFETY_VIOLATION, [container `codetoanalyze.java.checkers.Containers.mList` via call to `clear`]
codetoanalyze/java/threadsafety/Containers.java, void Containers.listRemoveBad1(int), 1, THREAD_SAFETY_VIOLATION, [container `codetoanalyze.java.checkers.Containers.mList` via call to `remove`]
codetoanalyze/java/threadsafety/Containers.java, void Containers.listRemoveBad2(String), 1, THREAD_SAFETY_VIOLATION, [container `codetoanalyze.java.checkers.Containers.mList` via call to `remove`]
codetoanalyze/java/threadsafety/Containers.java, void Containers.listSetBad(int,String), 1, THREAD_SAFETY_VIOLATION, [container `codetoanalyze.java.checkers.Containers.mList` via call to `set`]
codetoanalyze/java/threadsafety/Containers.java, void Containers.listSubclassWriteBad(ArrayList,int), 1, THREAD_SAFETY_VIOLATION, [container `&list` via call to `remove`]
codetoanalyze/java/threadsafety/Containers.java, void Containers.mapClearBad(), 1, THREAD_SAFETY_VIOLATION, [container `codetoanalyze.java.checkers.Containers.mMap` via call to `clear`]
codetoanalyze/java/threadsafety/Containers.java, void Containers.mapPutAllBad(Map), 1, THREAD_SAFETY_VIOLATION, [container `codetoanalyze.java.checkers.Containers.mMap` via call to `putAll`]
codetoanalyze/java/threadsafety/Containers.java, void Containers.mapPutBad(String,String), 1, THREAD_SAFETY_VIOLATION, [container `codetoanalyze.java.checkers.Containers.mMap` via call to `put`]
codetoanalyze/java/threadsafety/Containers.java, void Containers.mapRemoveBad(String), 1, THREAD_SAFETY_VIOLATION, [container `codetoanalyze.java.checkers.Containers.mMap` via call to `remove`]
codetoanalyze/java/threadsafety/Containers.java, void Containers.mapSubclassWriteBad(HashMap,String), 1, THREAD_SAFETY_VIOLATION, [container `&m` via call to `remove`]
codetoanalyze/java/threadsafety/Containers.java, void Containers.poolBad(), 5, THREAD_SAFETY_VIOLATION, [container `codetoanalyze.java.checkers.Containers.simplePool` via call to `release`]
codetoanalyze/java/threadsafety/Containers.java, void Containers.addToSimpleArrayMapBad(SimpleArrayMap), 1, THREAD_SAFETY_VIOLATION, [Write to container `&map` via call to `put`]
codetoanalyze/java/threadsafety/Containers.java, void Containers.addToSparseArrayBad(SparseArray), 1, THREAD_SAFETY_VIOLATION, [Write to container `&sparseArray` via call to `put`]
codetoanalyze/java/threadsafety/Containers.java, void Containers.addToSparseArrayCompatBad(SparseArrayCompat), 1, THREAD_SAFETY_VIOLATION, [Write to container `&sparseArray` via call to `put`]
codetoanalyze/java/threadsafety/Containers.java, void Containers.containerWrapperUnownedWriteBad(Object), 1, THREAD_SAFETY_VIOLATION, [call to Object ContainerWrapper.write(Object),call to Object ContainerWrapper._write(Object),Write to container `&this.codetoanalyze.java.checkers.ContainerWrapper.children` via call to `add`]
codetoanalyze/java/threadsafety/Containers.java, void Containers.listAddAllBad(Collection), 1, THREAD_SAFETY_VIOLATION, [Write to container `&this.codetoanalyze.java.checkers.Containers.mList` via call to `addAll`]
codetoanalyze/java/threadsafety/Containers.java, void Containers.listAddBad1(String), 1, THREAD_SAFETY_VIOLATION, [Write to container `&this.codetoanalyze.java.checkers.Containers.mList` via call to `add`]
codetoanalyze/java/threadsafety/Containers.java, void Containers.listAddBad2(int,String), 1, THREAD_SAFETY_VIOLATION, [Write to container `&this.codetoanalyze.java.checkers.Containers.mList` via call to `add`]
codetoanalyze/java/threadsafety/Containers.java, void Containers.listClearBad(), 1, THREAD_SAFETY_VIOLATION, [Write to container `&this.codetoanalyze.java.checkers.Containers.mList` via call to `clear`]
codetoanalyze/java/threadsafety/Containers.java, void Containers.listRemoveBad1(int), 1, THREAD_SAFETY_VIOLATION, [Write to container `&this.codetoanalyze.java.checkers.Containers.mList` via call to `remove`]
codetoanalyze/java/threadsafety/Containers.java, void Containers.listRemoveBad2(String), 1, THREAD_SAFETY_VIOLATION, [Write to container `&this.codetoanalyze.java.checkers.Containers.mList` via call to `remove`]
codetoanalyze/java/threadsafety/Containers.java, void Containers.listSetBad(int,String), 1, THREAD_SAFETY_VIOLATION, [Write to container `&this.codetoanalyze.java.checkers.Containers.mList` via call to `set`]
codetoanalyze/java/threadsafety/Containers.java, void Containers.listSubclassWriteBad(ArrayList,int), 1, THREAD_SAFETY_VIOLATION, [Write to container `&list` via call to `remove`]
codetoanalyze/java/threadsafety/Containers.java, void Containers.mapClearBad(), 1, THREAD_SAFETY_VIOLATION, [Write to container `&this.codetoanalyze.java.checkers.Containers.mMap` via call to `clear`]
codetoanalyze/java/threadsafety/Containers.java, void Containers.mapPutAllBad(Map), 1, THREAD_SAFETY_VIOLATION, [Write to container `&this.codetoanalyze.java.checkers.Containers.mMap` via call to `putAll`]
codetoanalyze/java/threadsafety/Containers.java, void Containers.mapPutBad(String,String), 1, THREAD_SAFETY_VIOLATION, [Write to container `&this.codetoanalyze.java.checkers.Containers.mMap` via call to `put`]
codetoanalyze/java/threadsafety/Containers.java, void Containers.mapRemoveBad(String), 1, THREAD_SAFETY_VIOLATION, [Write to container `&this.codetoanalyze.java.checkers.Containers.mMap` via call to `remove`]
codetoanalyze/java/threadsafety/Containers.java, void Containers.mapSubclassWriteBad(HashMap,String), 1, THREAD_SAFETY_VIOLATION, [Write to container `&m` via call to `remove`]
codetoanalyze/java/threadsafety/Containers.java, void Containers.poolBad(), 5, THREAD_SAFETY_VIOLATION, [Write to container `&this.codetoanalyze.java.checkers.Containers.simplePool` via call to `release`]
codetoanalyze/java/threadsafety/DeDup.java, void DeDup.colocated_read_write(), 1, THREAD_SAFETY_VIOLATION, [<Beginning of read trace>,call to void DeDup.read_and_write(),access to `codetoanalyze.java.checkers.DeDup.colocated_read`,<Beginning of write trace>,access to `codetoanalyze.java.checkers.DeDup.colocated_read`]
codetoanalyze/java/threadsafety/DeDup.java, void DeDup.separate_write_to_colocated_read(), 1, THREAD_SAFETY_VIOLATION, [access to `codetoanalyze.java.checkers.DeDup.colocated_read`]
codetoanalyze/java/threadsafety/DeDup.java, void DeDup.twoWritesOneInCaller(), 2, THREAD_SAFETY_VIOLATION, [access to `codetoanalyze.java.checkers.DeDup.field`]

Loading…
Cancel
Save