diff --git a/infer/src/checkers/ThreadSafety.ml b/infer/src/checkers/ThreadSafety.ml index 8d41e50c2..bd8e0b8fa 100644 --- a/infer/src/checkers/ThreadSafety.ml +++ b/infer/src/checkers/ThreadSafety.ml @@ -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) - | InterfaceCall _ as access - -> F.fprintf fmt "%a" ThreadSafetyDomain.Access.pp access + 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 - 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 + let sink_pname = CallSite.pname (ThreadSafetyDomain.PathDomain.Sink.call_site sink) in + match ThreadSafetyDomain.PathDomain.Sink.kind sink with + | Read _ | Write _ + -> 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 - | AccessPath.ArrayAccess _ - -> false - | AccessPath.FieldAccess fld - -> String.is_substring ~substring:"$SwitchMap" (Typ.Fieldname.to_string fld) - in - List.exists path ~f:check_access_step +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 diff --git a/infer/src/checkers/ThreadSafetyDomain.ml b/infer/src/checkers/ThreadSafetyDomain.ml index 63be40ff5..ac889aa8e 100644 --- a/infer/src/checkers/ThreadSafetyDomain.ml +++ b/infer/src/checkers/ThreadSafetyDomain.ml @@ -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 diff --git a/infer/src/checkers/ThreadSafetyDomain.mli b/infer/src/checkers/ThreadSafetyDomain.mli index abed57c13..3b170c446 100644 --- a/infer/src/checkers/ThreadSafetyDomain.mli +++ b/infer/src/checkers/ThreadSafetyDomain.mli @@ -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 diff --git a/infer/tests/codetoanalyze/java/threadsafety/issues.exp b/infer/tests/codetoanalyze/java/threadsafety/issues.exp index e19373eab..4eaa02cad 100644 --- a/infer/tests/codetoanalyze/java/threadsafety/issues.exp +++ b/infer/tests/codetoanalyze/java/threadsafety/issues.exp @@ -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.(Object),access to `Constructors.staticField`] codetoanalyze/java/threadsafety/Constructors.java, Constructors.(), 1, THREAD_SAFETY_VIOLATION, [access to `Constructors.staticField`] codetoanalyze/java/threadsafety/Constructors.java, Constructors.(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, [,call to void DeDup.read_and_write(),access to `codetoanalyze.java.checkers.DeDup.colocated_read`,,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`]