diff --git a/facebook-clang-plugins b/facebook-clang-plugins index 3e46b786a..ba3d4b906 160000 --- a/facebook-clang-plugins +++ b/facebook-clang-plugins @@ -1 +1 @@ -Subproject commit 3e46b786ad058c917a0cb23e9d26c5aaaed816be +Subproject commit ba3d4b9066cc57c413581d2efe909add94b4366f diff --git a/infer/lib/python/inferlib/issues.py b/infer/lib/python/inferlib/issues.py index a44aa48b2..2d9c36218 100644 --- a/infer/lib/python/inferlib/issues.py +++ b/infer/lib/python/inferlib/issues.py @@ -59,6 +59,7 @@ ISSUE_TYPES = [ 'REGISTERED_OBSERVER_BEING_DEALLOCATED', 'ASSIGN_POINTER_WARNING', 'GLOBAL_VARIABLE_INITIALIZED_WITH_FUNCTION_OR_METHOD_CALL', + 'UNSAFE_GUARDED_BY_ACCESS', ] NULL_STYLE_ISSUE_TYPES = [ diff --git a/infer/src/IR/Cfg.re b/infer/src/IR/Cfg.re index 8a8725452..a30ac612e 100644 --- a/infer/src/IR/Cfg.re +++ b/infer/src/IR/Cfg.re @@ -481,6 +481,7 @@ let module Node = { /** Return [true] iff the procedure is defined, and not just declared */ let proc_desc_is_defined proc_desc => proc_desc.pd_attributes.ProcAttributes.is_defined; + let proc_desc_is_java_synchroinized proc_desc => proc_desc.pd_attributes.ProcAttributes.is_java_synchronized_method; let proc_desc_get_loc proc_desc => proc_desc.pd_attributes.ProcAttributes.loc; /** Return name and type of formal parameters */ @@ -800,6 +801,7 @@ let module Procdesc = { let get_ret_var pdesc => Pvar.mk Ident.name_return (get_proc_name pdesc); let get_start_node = Node.proc_desc_get_start_node; let is_defined = Node.proc_desc_is_defined; + let is_java_synchronized = Node.proc_desc_is_java_synchroinized; let iter_nodes = Node.proc_desc_iter_nodes; let fold_calls = Node.proc_desc_fold_calls; let iter_calls = Node.proc_desc_iter_calls; diff --git a/infer/src/IR/Cfg.rei b/infer/src/IR/Cfg.rei index 92c9d385d..e37622c8d 100644 --- a/infer/src/IR/Cfg.rei +++ b/infer/src/IR/Cfg.rei @@ -87,6 +87,9 @@ let module Procdesc: { /** Return [true] iff the procedure is defined, and not just declared */ let is_defined: t => bool; + /** Return [true] if the procedure signature has the Java synchronized keyword */ + let is_java_synchronized: t => bool; + /** iterate over all the nodes of a procedure */ let iter_nodes: (node => unit) => t => unit; diff --git a/infer/src/backend/exceptions.ml b/infer/src/backend/exceptions.ml index 3c284c710..4173ce2d8 100644 --- a/infer/src/backend/exceptions.ml +++ b/infer/src/backend/exceptions.ml @@ -82,6 +82,7 @@ exception Tainted_value_reaching_sensitive_function of Localise.error_desc * L.m exception Unary_minus_applied_to_unsigned_expression of Localise.error_desc * L.ml_loc exception Uninitialized_value of Localise.error_desc * L.ml_loc exception Unknown_proc +exception Unsafe_guarded_by_access of Localise.error_desc * L.ml_loc exception Use_after_free of Localise.error_desc * L.ml_loc exception Wrong_argument_number of L.ml_loc @@ -287,6 +288,9 @@ let recognize_exception exn = | Unknown_proc -> (Localise.from_string "Unknown_proc", Localise.no_desc, None, Exn_developer, Low, None, Nocat) + | Unsafe_guarded_by_access (desc, ml_loc) -> + (Localise.unsafe_guarded_by_access, + desc, Some ml_loc, Exn_user, High, None, Prover) | Use_after_free (desc, ml_loc) -> (Localise.use_after_free, desc, Some ml_loc, Exn_user, High, None, Prover) diff --git a/infer/src/backend/exceptions.mli b/infer/src/backend/exceptions.mli index 776067fb2..ef81e48f9 100644 --- a/infer/src/backend/exceptions.mli +++ b/infer/src/backend/exceptions.mli @@ -82,6 +82,7 @@ exception Tainted_value_reaching_sensitive_function of Localise.error_desc * Log exception Unary_minus_applied_to_unsigned_expression of Localise.error_desc * Logging.ml_loc exception Uninitialized_value of Localise.error_desc * Logging.ml_loc exception Unknown_proc +exception Unsafe_guarded_by_access of Localise.error_desc * Logging.ml_loc exception Use_after_free of Localise.error_desc * Logging.ml_loc exception Wrong_argument_number of Logging.ml_loc diff --git a/infer/src/backend/localise.ml b/infer/src/backend/localise.ml index 2d8c51020..ee99d596a 100644 --- a/infer/src/backend/localise.ml +++ b/infer/src/backend/localise.ml @@ -70,6 +70,7 @@ let skip_pointer_dereference = "SKIP_POINTER_DEREFERENCE" let stack_variable_address_escape = "STACK_VARIABLE_ADDRESS_ESCAPE" let tainted_value_reaching_sensitive_function = "TAINTED_VALUE_REACHING_SENSITIVE_FUNCTION" let unary_minus_applied_to_unsigned_expression = "UNARY_MINUS_APPLIED_TO_UNSIGNED_EXPRESSION" +let unsafe_guarded_by_access = "UNSAFE_GUARDED_BY_ACCESS" let uninitialized_value = "UNINITIALIZED_VALUE" let use_after_free = "USE_AFTER_FREE" @@ -432,6 +433,23 @@ let desc_context_leak pname context_typ fieldname leak_path : error_desc = "Context " ^ context_str ^ "may leak during method " ^ pname_str ^ ":\n" in { no_desc with descriptions = [preamble; leak_root; path_str] } +let desc_unsafe_guarded_by_access pname accessed_fld guarded_by_str loc = + let line_info = at_line (Tags.create ()) loc in + let accessed_fld_str = Ident.fieldname_to_string accessed_fld in + let annot_str = Printf.sprintf "`@GuardedBy(\"%s\")`" guarded_by_str in + let msg = + Printf.sprintf + "The field `%s` is annotated with %s, but the lock `%s` is not held during the access to the field `%s`. Consider wrapping the access in a `synchronized(%s)` block or annotating %s with %s" + accessed_fld_str + annot_str + guarded_by_str + line_info + guarded_by_str + (Procname.to_string pname) + annot_str in + { no_desc with descriptions = [msg]; } + + let desc_fragment_retains_view fragment_typ fieldname fld_typ pname : error_desc = (* TODO: try advice *) let problem = diff --git a/infer/src/backend/localise.mli b/infer/src/backend/localise.mli index 1b6df79dc..2b57e6dd0 100644 --- a/infer/src/backend/localise.mli +++ b/infer/src/backend/localise.mli @@ -65,6 +65,7 @@ val return_statement_missing : t val stack_variable_address_escape : t val unary_minus_applied_to_unsigned_expression : t val uninitialized_value : t +val unsafe_guarded_by_access : t val use_after_free : t val skip_function : t val skip_pointer_dereference : t @@ -257,6 +258,9 @@ val desc_inherently_dangerous_function : Procname.t -> error_desc val desc_unary_minus_applied_to_unsigned_expression : string option -> string -> Location.t -> error_desc +val desc_unsafe_guarded_by_access : + Procname.t -> Ident.fieldname -> string -> Location.t -> error_desc + val desc_tainted_value_reaching_sensitive_function : Sil.taint_kind -> string -> Procname.t -> Procname.t -> Location.t -> error_desc diff --git a/infer/src/backend/rearrange.ml b/infer/src/backend/rearrange.ml index 7bd6f7372..414f8c0d6 100644 --- a/infer/src/backend/rearrange.ml +++ b/infer/src/backend/rearrange.ml @@ -600,12 +600,12 @@ let prop_iter_add_hpred_footprint_to_prop pname tenv prop (lexp, typ) inst = let offsets_default = Sil.exp_get_offsets lexp in Prop.prop_iter_set_state iter offsets_default -(* TODO: have to call this on both reads and writes *) -let add_guarded_by_constraints prop lexp = +(** If [lexp] is an access to a field that is annotated with @GuardedBy, add constraints to [prop] + expressing the safety conditions for the access. Complain if these conditions cannot be met. *) +let add_guarded_by_constraints prop lexp pdesc = let sigma = Prop.get_sigma prop in - (** if [fld] is annotated with @GuardedBy("mLock"), return mLock *) - let get_guarded_by_fld_str fld typ = - let extract_guarded_by_str (annot, _) = + let extract_guarded_by_str item_annot = + let annot_extract_guarded_by_str (annot, _) = if Annotations.annot_ends_with annot Annotations.guarded_by then match annot.Sil.parameters with @@ -613,53 +613,95 @@ let add_guarded_by_constraints prop lexp = | _ -> None else None in + IList.find_map_opt annot_extract_guarded_by_str item_annot in + (** if [fld] is annotated with @GuardedBy("mLock"), return mLock *) + let get_guarded_by_fld_str fld typ = match Annotations.get_field_type_and_annotation fld typ with - | Some (_, item_annot) -> IList.find_map_opt extract_guarded_by_str item_annot + | Some (_, item_annot) -> extract_guarded_by_str item_annot | _ -> None in + let guarded_by_str_is_this guarded_by_str = + guarded_by_str = "this" in (* find A.guarded_by_fld_str |-> B and return Some B, or None if there is no such hpred *) - let find_guarded_by_exp fld typ sigma = - match get_guarded_by_fld_str fld typ with - | Some guarded_by_fld_str -> - let extract_guarded_by_strexp (fld, strexp) = - (* this comparison needs to be somewhat fuzzy, since programmers are free to write - @GuardedBy("mLock"), @GuardedBy("MyClass.mLock"), or use other conventions *) - if Ident.fieldname_to_flat_string fld = guarded_by_fld_str || - Ident.fieldname_to_string fld = guarded_by_fld_str - then Some strexp - else None in - IList.find_map_opt - (function - | Sil.Hpointsto (_, Estruct (flds, _), _) -> - IList.find_map_opt extract_guarded_by_strexp flds - | _ -> - None) - sigma - | None -> - None in + let find_guarded_by_exp guarded_by_str sigma = + let extract_guarded_by_strexp (fld, strexp) = + (* this comparison needs to be somewhat fuzzy, since programmers are free to write + @GuardedBy("mLock"), @GuardedBy("MyClass.mLock"), or use other conventions *) + if Ident.fieldname_to_flat_string fld = guarded_by_str || + Ident.fieldname_to_string fld = guarded_by_str + then Some strexp + else None in + IList.find_map_opt + (function + | Sil.Hpointsto (_, Estruct (flds, _), _) -> + IList.find_map_opt extract_guarded_by_strexp flds + | Sil.Hpointsto (Lvar pvar, rhs_exp, _) + when guarded_by_str_is_this guarded_by_str && Pvar.is_this pvar -> + Some rhs_exp + | _ -> + None) + sigma in + (* warn if the access to [lexp] is not protected by the [guarded_by_fld_str] lock *) + let enforce_guarded_access accessed_fld guarded_by_str prop = + (* return true if [pdesc] has an annotation that matches [guarded_by_str] *) + let proc_has_matching_annot pdesc guarded_by_str = + let proc_signature = + Annotations.get_annotated_signature (Cfg.Procdesc.get_attributes pdesc) in + let proc_annot, _ = proc_signature.Annotations.ret in + match extract_guarded_by_str proc_annot with + | Some proc_guarded_by_str -> + (* the lock is not held, but the procedure is annotated with @GuardedBy *) + proc_guarded_by_str = guarded_by_str + | None -> false in + let warn pdesc accessed_fld guarded_by_str = + let pname = Cfg.Procdesc.get_proc_name pdesc in + let loc = State.get_loc () in + let err_desc = + Localise.desc_unsafe_guarded_by_access pname accessed_fld guarded_by_str loc in + let exn = Exceptions.Unsafe_guarded_by_access (err_desc, __POS__) in + Reporting.log_error pname exn in + match find_guarded_by_exp guarded_by_str (Prop.get_sigma prop) with + | Some (Sil.Eexp (guarded_by_exp, _)) -> + let has_lock = + (* procedure is synchronized and guarded by this *) + (guarded_by_str_is_this guarded_by_str && Cfg.Procdesc.is_java_synchronized pdesc) || + (* or the prop says we already have the lock *) + IList.exists + (function + | Sil.Alocked -> true + | _ -> false) + (Prop.get_exp_attributes prop guarded_by_exp) in + if has_lock + then + (* we have the lock; no need to add a proof obligation *) + (* TODO: materialize [fld], but don't add [fld] to the footprint. *) + prop + else + if proc_has_matching_annot pdesc guarded_by_str + then + (* procedure is annotated and holding the same lock as the field. add locked proof + obligation to the current *) + (* TODO: materialize [fld], but don't add [fld] to the footprint. *) + let locked_attr = Sil.Const (Cattribute Alocked) in + Prop.conjoin_neq ~footprint:true guarded_by_exp locked_attr prop + else + begin + (* lock is not held. warn *) + warn pdesc accessed_fld guarded_by_str; + prop + end + | _ -> + if not (proc_has_matching_annot pdesc guarded_by_str) + then + (* can't find the object the annotation refers to, and procedure is not annotated. warn *) + warn pdesc accessed_fld guarded_by_str + else ();(* TODO: add proof obligation here *) + prop in let check_fld_locks typ prop_acc (fld, strexp) = match strexp with | Sil.Eexp (exp, _) when Sil.exp_equal exp lexp -> begin - match find_guarded_by_exp fld typ sigma with - | Some (Sil.Eexp (guarded_by_exp, _)) -> - let has_lock = - IList.exists - (function - | Sil.Alocked -> true - | _ -> false) - (Prop.get_exp_attributes prop_acc guarded_by_exp) in - if has_lock - then - (* we have the lock; no need to add a proof obligation *) - (* TODO: materialize [fld], but don't add [fld] to the footprint. *) - prop_acc - else - (* the lock is not known to be held. add a "lock held" proof obligation *) - (* TODO: materialize [fld], but don't add [fld] to the footprint. *) - let locked_attr = Sil.Const (Cattribute Alocked) in - Prop.conjoin_neq ~footprint:true guarded_by_exp locked_attr prop_acc - | _ -> - (* field not guarded; proceed with abduction as we normally would *) - prop_acc + match get_guarded_by_fld_str fld typ with + | Some guarded_by_fld_str -> enforce_guarded_access fld guarded_by_fld_str prop_acc + | None -> prop_acc (* field not annotated; proceed as normal *) end | _ -> prop_acc in @@ -1199,7 +1241,7 @@ let rearrange ?(report_deref_errors=true) pdesc tenv lexp typ prop loc let pname = Cfg.Procdesc.get_proc_name pdesc in let prop' = if Config.csl_analysis && !Config.footprint && Procname.is_java pname - then add_guarded_by_constraints prop lexp + then add_guarded_by_constraints prop lexp pdesc else prop in match Prop.prop_iter_create prop' with | None -> diff --git a/infer/tests/build_systems/expected_outputs/ant_report.json b/infer/tests/build_systems/expected_outputs/ant_report.json index 30b53189a..bf9bdceeb 100644 --- a/infer/tests/build_systems/expected_outputs/ant_report.json +++ b/infer/tests/build_systems/expected_outputs/ant_report.json @@ -729,6 +729,41 @@ "file": "codetoanalyze/java/infer/ResourceLeaks.java", "procedure": "int ResourceLeaks.readConfigNotCloseStream(String)" }, + { + "bug_type": "UNSAFE_GUARDED_BY_ACCESS", + "file": "codetoanalyze/java/infer/GuardedByExample.java", + "procedure": "void GuardedByExample.readFAfterBlockBad()" + }, + { + "bug_type": "UNSAFE_GUARDED_BY_ACCESS", + "file": "codetoanalyze/java/infer/GuardedByExample.java", + "procedure": "void GuardedByExample.readFBad()" + }, + { + "bug_type": "UNSAFE_GUARDED_BY_ACCESS", + "file": "codetoanalyze/java/infer/GuardedByExample.java", + "procedure": "void GuardedByExample.readFBadWrongAnnotation()" + }, + { + "bug_type": "UNSAFE_GUARDED_BY_ACCESS", + "file": "codetoanalyze/java/infer/GuardedByExample.java", + "procedure": "void GuardedByExample.readFBadWrongLock()" + }, + { + "bug_type": "UNSAFE_GUARDED_BY_ACCESS", + "file": "codetoanalyze/java/infer/GuardedByExample.java", + "procedure": "void GuardedByExample.readGFromCopyBad()" + }, + { + "bug_type": "UNSAFE_GUARDED_BY_ACCESS", + "file": "codetoanalyze/java/infer/GuardedByExample.java", + "procedure": "void GuardedByExample.readHBad()" + }, + { + "bug_type": "UNSAFE_GUARDED_BY_ACCESS", + "file": "codetoanalyze/java/infer/GuardedByExample.java", + "procedure": "void GuardedByExample.readHBadSynchronizedMethodShouldntHelp()" + }, { "bug_type": "RESOURCE_LEAK", "file": "codetoanalyze/java/infer/ResourceLeaks.java", @@ -774,6 +809,11 @@ "file": "codetoanalyze/java/infer/CloseableAsResourceExample.java", "procedure": "T CloseableAsResourceExample.sourceOfNullWithResourceLeak()" }, + { + "bug_type": "UNSAFE_GUARDED_BY_ACCESS", + "file": "codetoanalyze/java/infer/GuardedByExample.java", + "procedure": "void GuardedByExample.synchronizedMethodBad()" + }, { "bug_type": "NULL_DEREFERENCE", "file": "codetoanalyze/java/infer/NullPointerExceptions.java", diff --git a/infer/tests/build_systems/expected_outputs/buck_report.json b/infer/tests/build_systems/expected_outputs/buck_report.json index fb40efc16..7b6ebec01 100644 --- a/infer/tests/build_systems/expected_outputs/buck_report.json +++ b/infer/tests/build_systems/expected_outputs/buck_report.json @@ -734,6 +734,41 @@ "file": "infer/tests/codetoanalyze/java/infer/ResourceLeaks.java", "procedure": "int ResourceLeaks.readConfigNotCloseStream(String)" }, + { + "bug_type": "UNSAFE_GUARDED_BY_ACCESS", + "file": "infer/tests/codetoanalyze/java/infer/GuardedByExample.java", + "procedure": "void GuardedByExample.readFAfterBlockBad()" + }, + { + "bug_type": "UNSAFE_GUARDED_BY_ACCESS", + "file": "infer/tests/codetoanalyze/java/infer/GuardedByExample.java", + "procedure": "void GuardedByExample.readFBad()" + }, + { + "bug_type": "UNSAFE_GUARDED_BY_ACCESS", + "file": "infer/tests/codetoanalyze/java/infer/GuardedByExample.java", + "procedure": "void GuardedByExample.readFBadWrongAnnotation()" + }, + { + "bug_type": "UNSAFE_GUARDED_BY_ACCESS", + "file": "infer/tests/codetoanalyze/java/infer/GuardedByExample.java", + "procedure": "void GuardedByExample.readFBadWrongLock()" + }, + { + "bug_type": "UNSAFE_GUARDED_BY_ACCESS", + "file": "infer/tests/codetoanalyze/java/infer/GuardedByExample.java", + "procedure": "void GuardedByExample.readGFromCopyBad()" + }, + { + "bug_type": "UNSAFE_GUARDED_BY_ACCESS", + "file": "infer/tests/codetoanalyze/java/infer/GuardedByExample.java", + "procedure": "void GuardedByExample.readHBad()" + }, + { + "bug_type": "UNSAFE_GUARDED_BY_ACCESS", + "file": "infer/tests/codetoanalyze/java/infer/GuardedByExample.java", + "procedure": "void GuardedByExample.readHBadSynchronizedMethodShouldntHelp()" + }, { "bug_type": "RESOURCE_LEAK", "file": "infer/tests/codetoanalyze/java/infer/ResourceLeaks.java", @@ -779,6 +814,11 @@ "file": "infer/tests/codetoanalyze/java/infer/CloseableAsResourceExample.java", "procedure": "T CloseableAsResourceExample.sourceOfNullWithResourceLeak()" }, + { + "bug_type": "UNSAFE_GUARDED_BY_ACCESS", + "file": "infer/tests/codetoanalyze/java/infer/GuardedByExample.java", + "procedure": "void GuardedByExample.synchronizedMethodBad()" + }, { "bug_type": "NULL_DEREFERENCE", "file": "infer/tests/codetoanalyze/java/infer/NullPointerExceptions.java", diff --git a/infer/tests/codetoanalyze/java/infer/GuardedByExample.java b/infer/tests/codetoanalyze/java/infer/GuardedByExample.java new file mode 100644 index 000000000..45b9e5105 --- /dev/null +++ b/infer/tests/codetoanalyze/java/infer/GuardedByExample.java @@ -0,0 +1,103 @@ +/* + * 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. + */ + +package codetoanalyze.java.infer; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +public class GuardedByExample { + + @Retention(RetentionPolicy.CLASS) + @Target({ElementType.FIELD, ElementType.METHOD}) + public @interface GuardedBy { + String value(); + } + + private Object mLock = new Object(); + + private Object mOtherLock = new Object(); + + @GuardedBy("mLock") + Object f = new Object(); + + @GuardedBy("this") + Object g = new Object(); + + Object mCopyOfG; + + @GuardedBy("SomeLockThatDoesntExist") + Object h = new Object(); + + void readFBad() { + this.f.toString(); + } + + void readFBadWrongLock() { + synchronized (mOtherLock) { + this.f.toString(); // f is supposed to be protected by mLock + } + } + + void readFAfterBlockBad() { + synchronized (mLock) { + } + this.f.toString(); + } + + @GuardedBy("mOtherLock") + void readFBadWrongAnnotation() { + this.f.toString(); + } + + @GuardedBy("mLock") + void readFOkMethodAnnotated() { + this.f.toString(); + } + + @GuardedBy("this") + void guardedByThisOk() { + this.g.toString(); + } + + synchronized void synchronizedMethodOk() { + this.g.toString(); + } + + void readFOkSynchronized() { + synchronized (mLock) { + this.f.toString(); + } + } + + synchronized void synchronizedMethodBad() { + this.f.toString(); // f is supposed to be protected by mLock, not this + } + + void readGFromCopyBad() { + synchronized (this) { + mCopyOfG = g; // these are ok: access of g guarded by this + g.toString(); + } + mCopyOfG.toString(); // should be an error; unprotected access to pt(g) + } + + void readHBad() { + synchronized (mLock) { // h is not protected by mLock + this.h.toString(); + } + } + + synchronized void readHBadSynchronizedMethodShouldntHelp() { + this.h.toString(); // h is not protected by this + } + +} diff --git a/infer/tests/endtoend/java/infer/GuardedByTest.java b/infer/tests/endtoend/java/infer/GuardedByTest.java new file mode 100644 index 000000000..4bd7a336e --- /dev/null +++ b/infer/tests/endtoend/java/infer/GuardedByTest.java @@ -0,0 +1,63 @@ +/* + * 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. + */ + +package endtoend.java.infer; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsExactly.containsExactly; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class GuardedByTest { + + public static final String SOURCE_FILE = + "infer/tests/codetoanalyze/java/infer/GuardedByExample.java"; + + public static final String UNSAFE_GUARDED_BY_ACCESS = "UNSAFE_GUARDED_BY_ACCESS"; + + private static InferResults inferResults; + + @BeforeClass + public static void loadResults() throws InterruptedException, IOException { + inferResults = InferResults.loadInferResults( + GuardedByTest.class, + SOURCE_FILE); + } + + @Test + public void matchErrors() + throws IOException, InterruptedException, InferException { + String[] methods = { + "readFBad", + "readFBadWrongLock", + "readFAfterBlockBad", + "readFBadWrongAnnotation", + "synchronizedMethodBad", + "readGFromCopyBad", + "readHBad", + "readHBadSynchronizedMethodShouldntHelp", + }; + assertThat( + "Results should contain " + UNSAFE_GUARDED_BY_ACCESS, + inferResults, + containsExactly( + UNSAFE_GUARDED_BY_ACCESS, + SOURCE_FILE, + methods + ) + ); + } + +}