/*
* Copyright 2015 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.errorprone.bugpatterns.threadsafety;
import com.google.common.io.ByteStreams;
import com.google.errorprone.CompilationTestHelper;
import com.google.errorprone.annotations.Immutable;
import com.google.errorprone.annotations.concurrent.LazyInit;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** {@link ImmutableChecker}Test */
@RunWith(JUnit4.class)
public class ImmutableCheckerTest {
private final CompilationTestHelper compilationHelper =
CompilationTestHelper.newInstance(ImmutableChecker.class, getClass());
@Test
public void basicFields() {
compilationHelper
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"import com.google.common.collect.ImmutableList;",
"@Immutable class Test {",
" final int a = 42;",
" final String b = null;",
" final java.lang.String c = null;",
" final com.google.common.collect.ImmutableList<String> d = null;",
" final ImmutableList<Integer> e = null;",
" final Deprecated dep = null;",
" final Class<?> clazz = Class.class;",
"}")
.doTest();
}
@Test
public void interfacesMutableByDefault() {
compilationHelper
.addSourceLines("I.java", "interface I {}")
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable class Test {",
" // BUG: Diagnostic contains: 'I' is not annotated @Immutable",
" private final I i = new I() {};",
"}")
.doTest();
}
@Test
public void annotationsAreImmutable() {
compilationHelper
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable @interface Test {}")
.doTest();
}
@Test
public void customAnnotationsMightBeMutable() {
compilationHelper
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable @interface Test {}")
.addSourceLines(
"MyTest.java",
"import com.google.errorprone.annotations.Immutable;",
"import java.lang.annotation.Annotation;",
"@Immutable final class MyTest implements Test {",
" // BUG: Diagnostic contains: non-final",
" public Object[] xs = {};",
" public Class<? extends Annotation> annotationType() {",
" return null;",
" }",
"}")
.doTest();
}
@Ignore("b/25630189") // don't check annotations for immutability yet
@Test
public void customImplementionsOfImplicitlyImmutableAnnotationsMustBeImmutable() {
compilationHelper
.addSourceLines("Anno.java", "@interface Anno {}")
.addSourceLines(
"MyAnno.java",
"import java.lang.annotation.Annotation;",
"final class MyAnno implements Anno {",
" // BUG: Diagnostic contains:",
" public Object[] xs = {};",
" public Class<? extends Annotation> annotationType() {",
" return null;",
" }",
"}")
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable class Test {",
" private final Anno anno = new MyAnno();",
"}")
.doTest();
}
@Test
public void customAnnotationsSubtype() {
compilationHelper
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable @interface Test {}")
.addSourceLines(
"MyTest.java",
"import java.lang.annotation.Annotation;",
"// BUG: Diagnostic contains:",
"// extends @Immutable type Test, but is not annotated as immutable",
"final class MyTest implements Test {",
" public Object[] xs = {};",
" public Class<? extends Annotation> annotationType() {",
" return null;",
" }",
"}")
.doTest();
}
@Test
public void annotationsDefaultToImmutable() {
compilationHelper
.addSourceLines(
"Test.java",
"import javax.lang.model.element.ElementKind;",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable class Test {",
" private final Override override = null;",
"}")
.doTest();
}
@Test
public void enumsDefaultToImmutable() {
compilationHelper
.addSourceLines(
"Test.java",
"import javax.lang.model.element.ElementKind;",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable class Test {",
" private final ElementKind ek = null;",
"}")
.doTest();
}
@Test
public void enumsMayBeImmutable() {
compilationHelper
.addSourceLines(
"Kind.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable enum Kind { A, B, C; }")
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable class Test {",
" private final Kind k = null;",
"}")
.doTest();
}
@Test
public void mutableArray() {
compilationHelper
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable class Test {",
" // BUG: Diagnostic contains:",
" final int[] xs = {42};",
"}")
.doTest();
}
@Test
public void annotatedImmutableInterfaces() {
compilationHelper
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable interface Test {}")
.doTest();
}
@Test
public void immutableInterfaceField() {
compilationHelper
.addSourceLines(
"MyInterface.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable interface MyInterface {}")
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable class Test {",
" final MyInterface i = null;",
"}")
.doTest();
}
@Test
public void deeplyImmutableArguments() {
compilationHelper
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"import com.google.common.collect.ImmutableList;",
"@Immutable class Test {",
" final ImmutableList<ImmutableList<ImmutableList<String>>> l = null;",
"}")
.doTest();
}
@Test
public void mutableNonFinalField() {
compilationHelper
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable class Test {",
" // BUG: Diagnostic contains: non-final",
" int a = 42;",
"}")
.doTest();
}
@Test
public void ignoreStaticFields() {
compilationHelper
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable class Test {",
" static int a = 42;",
"}")
.doTest();
}
@Test
public void mutableField() {
compilationHelper
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"import java.util.Map;",
"@Immutable class Test {",
" // BUG: Diagnostic contains:",
" final Map<String, String> a = null;",
"}")
.doTest();
}
@Test
public void deeplyMutableTypeArguments() {
compilationHelper
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"import java.util.Map;",
"import com.google.common.collect.ImmutableList;",
"@Immutable class Test {",
" // BUG: Diagnostic contains: instantiated with mutable type for 'E'",
" final ImmutableList<ImmutableList<ImmutableList<Map<String, String>>>> l = null;",
"}")
.doTest();
}
@Test
public void rawImpliesImmutable() {
compilationHelper
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"import com.google.common.collect.ImmutableList;",
"@Immutable class Test {",
" // BUG: Diagnostic contains:",
" final ImmutableList l = null;",
"}")
.doTest();
}
@Test
public void extendsImmutable() {
compilationHelper
.addSourceLines(
"Super.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable public class Super {",
" public final int x = 42;",
"}")
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable class Test extends Super {",
"}")
.doTest();
}
@Test
public void extendsMutable() {
compilationHelper
.addSourceLines("Super.java", "public class Super {", " public int x = 42;", "}")
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"// BUG: Diagnostic contains: 'Super' has non-final field 'x'",
"@Immutable class Test extends Super {",
"}")
.doTest();
}
@Test
public void extendsImmutableAnnotated_substBounds() {
compilationHelper
.addSourceLines(
"SuperMost.java",
"import com.google.errorprone.annotations.Immutable;",
"public class SuperMost<B> {",
" public final B x = null;",
"}")
.addSourceLines(
"Super.java",
"import java.util.List;",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable(containerOf={\"A\"}) public class Super<A, B> extends SuperMost<A> {",
" public final int x = 42;",
"}")
.doTest();
}
@Test
public void typeParameterWithImmutableBound() {
compilationHelper
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"import com.google.common.collect.ImmutableList;",
"@Immutable(containerOf=\"T\") class Test<T extends ImmutableList<String>> {",
" final T t = null;",
"}")
.doTest();
}
@Test
public void immutableTypeArgumentInstantiation() {
compilationHelper
.addSourceLines(
"Holder.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable(containerOf=\"T\") public class Holder<T> {",
" public final T t = null;",
"}")
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable class Test {",
" final Holder<String> h = null;",
"}")
.doTest();
}
@Test
public void mutableTypeArgumentInstantiation() {
compilationHelper
.addSourceLines(
"Holder.java",
"import com.google.errorprone.annotations.Immutable;",
"public class Holder<T> {",
" public final T t = null;",
"}")
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable class Test {",
" // BUG: Diagnostic contains:",
" final Holder<Object> h = null;",
"}")
.doTest();
}
@Test
public void instantiationWithMutableType() {
compilationHelper
.addSourceLines(
"Holder.java",
"import com.google.errorprone.annotations.Immutable;",
"public class Holder<T> {",
" public final T t = null;",
"}")
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable class Test {",
" // BUG: Diagnostic contains: not annotated",
" final Holder<Object> h = null;",
"}")
.doTest();
}
@Test
public void transitiveSuperSubstitutionImmutable() {
compilationHelper
.addSourceLines(
"SuperMostType.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable(containerOf=\"N\") public class SuperMostType<N> {",
" public final N f = null;",
"}")
.addSourceLines(
"MiddleClass.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable(containerOf=\"M\") public class MiddleClass<M> extends SuperMostType<M> {",
" // Empty",
"}")
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable class Test extends MiddleClass<String> {",
" final MiddleClass<String> f = null;",
"}")
.doTest();
}
@Test
public void containerOf_mutableInstantiation() {
compilationHelper
.addSourceLines(
"X.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable(containerOf=\"V\") class X<V> {",
" private final V t = null;",
"}")
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable class Test<T> {",
" // BUG: Diagnostic contains:",
" // 'X' was instantiated with mutable type for 'V'",
" // 'T' is a mutable type variable",
" private final X<T> t = null;",
"}")
.doTest();
}
@Test
public void missingContainerOf() {
compilationHelper
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"import java.util.List;",
"@Immutable class Test<T> {",
" // BUG: Diagnostic contains: 'T' is a mutable type variable",
" private final T t = null;",
"}")
.doTest();
}
@Test
public void transitiveSuperSubstitutionMutable() {
compilationHelper
.addSourceLines(
"SuperMostType.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable(containerOf=\"N\") public class SuperMostType<N> {",
" public final N f = null;",
"}")
.addSourceLines(
"MiddleClass.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable(containerOf=\"M\") public class MiddleClass<M> extends SuperMostType<M> {",
" // Empty",
"}")
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"import java.util.List;",
"// BUG: Diagnostic contains: instantiated with mutable type for 'M'",
"@Immutable class Test extends MiddleClass<List> {",
"}")
.doTest();
}
@Test
public void immutableInstantiation() {
compilationHelper
.addSourceLines(
"X.java",
"import com.google.common.collect.ImmutableList;",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable(containerOf=\"T\") public class X<T> {",
" final ImmutableList<T> xs = null;",
"}")
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"import java.util.List;",
"@Immutable class Test {",
" final X<String> x = null;",
"}")
.doTest();
}
@Test
public void mutableInstantiation() {
compilationHelper
.addSourceLines(
"X.java",
"import com.google.common.collect.ImmutableList;",
"public class X<T> { final ImmutableList<T> xs = null; }")
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable class Test {",
"// BUG: Diagnostic contains:",
" final X<Object> x = null;",
"}")
.doTest();
}
@Test
public void immutableInstantiation_superBound() {
compilationHelper
.addSourceLines(
"X.java",
"import com.google.common.collect.ImmutableList;",
"public class X<T> { final ImmutableList<? super T> xs = null; }")
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"import java.util.List;",
"@Immutable class Test {",
" // BUG: Diagnostic contains:",
" final X<String> x = null;",
"}")
.doTest();
}
@Test
public void mutableInstantiation_superBound() {
compilationHelper
.addSourceLines(
"X.java",
"import com.google.common.collect.ImmutableList;",
"public class X<T> { final ImmutableList<? super T> xs = null; }")
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"import java.util.List;",
"@Immutable class Test {",
" // BUG: Diagnostic contains: is not annotated",
" final X<String> x = null;",
"}")
.doTest();
}
@Test
public void immutableInstantiation_extendsBound() {
compilationHelper
.addSourceLines(
"X.java",
"import com.google.common.collect.ImmutableList;",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable(containerOf=\"T\") public class X<T> {",
" final ImmutableList<? extends T> xs = null;",
"}")
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"import java.util.List;",
"@Immutable class Test {",
" final X<String> x = null;",
"}")
.doTest();
}
@Test
public void mutableInstantiation_wildcard() {
compilationHelper
.addSourceLines(
"X.java",
"import com.google.common.collect.ImmutableList;",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable(containerOf=\"T\") public class X<T> {",
" // BUG: Diagnostic contains: mutable type for 'E', 'Object' is mutable",
" final ImmutableList<?> xs = null;",
"}")
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"import java.util.List;",
"@Immutable class Test {",
" final X<String> x = null;",
"}")
.doTest();
}
@Test
public void mutableInstantiation_extendsBound() {
compilationHelper
.addSourceLines(
"X.java",
"import com.google.errorprone.annotations.Immutable;",
"import com.google.common.collect.ImmutableList;",
"@Immutable(containerOf=\"T\") public class X<T> {",
" final ImmutableList<? extends T> xs = null;",
"}")
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"import java.util.List;",
"@Immutable class Test {",
" // BUG: Diagnostic contains: instantiated with mutable type",
" final X<Object> x = null;",
"}")
.doTest();
}
@Test
public void containerOf_noSuchType() {
compilationHelper
.addSourceLines(
"X.java",
"import com.google.errorprone.annotations.Immutable;",
"// BUG: Diagnostic contains: could not find type(s) referenced by containerOf: Z",
"@Immutable(containerOf=\"Z\") public class X<T> {",
" final int xs = 1;",
"}")
.doTest();
}
@Test
public void immutableInstantiation_inferredImmutableType() {
compilationHelper
.addSourceLines(
"X.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable(containerOf=\"T\") public class X<T> {",
" final T xs = null;",
"}")
.addSourceLines(
"Y.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable(containerOf=\"T\") public class Y<T> {",
" final X<? extends T> xs = null;",
"}")
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable class Test {",
" final Y<String> x = null;",
"}")
.doTest();
}
@Test
public void mutableInstantiation_inferredImmutableType() {
compilationHelper
.addSourceLines("X.java", "public class X<T> {", " final T xs = null;", "}")
.addSourceLines("Y.java", "public class Y<T> {", " final X<? extends T> xs = null;", "}")
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable class Test {",
" // BUG: Diagnostic contains:",
" final Y<Object> x = null;",
"}")
.doTest();
}
@Test
public void mutableWildInstantiation() {
compilationHelper
.addSourceLines(
"X.java",
"import com.google.common.collect.ImmutableList;",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable(containerOf=\"T\") public class X<T> {",
" final ImmutableList<T> xs = null;",
"}")
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable class Test {",
" // BUG: Diagnostic contains: instantiated",
" final X<?> x = null;",
"}")
.doTest();
}
@Test
public void mutableRawType() {
compilationHelper
.addSourceLines(
"X.java",
"import com.google.common.collect.ImmutableList;",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable(containerOf=\"T\") public class X<T> {",
" final ImmutableList<T> xs = null;",
"}")
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable class Test {",
" // BUG: Diagnostic contains: raw",
" final X x = null;",
"}")
.doTest();
}
@Test
public void testImmutableListImplementation() {
compilationHelper
.addSourceLines(
"com/google/common/collect/ImmutableList.java",
"package com.google.common.collect;",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable class ImmutableList<E> {",
" public Object[] veryMutable = null;",
"}")
.doTest();
}
@Test
public void positiveAnonymous() {
compilationHelper
.addSourceLines(
"threadsafety/Super.java",
"package threadsafety;",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable class Super {",
"}")
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import com.google.errorprone.annotations.Immutable;",
"class Test {{",
" new Super() {",
" // BUG: Diagnostic contains: non-final",
" int x = 0;",
" {",
" x++;",
" }",
" };",
"}}")
.doTest();
}
@Test
public void positiveAnonymousInterface() {
compilationHelper
.addSourceLines(
"threadsafety/Super.java",
"package threadsafety;",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable interface Super {",
"}")
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import com.google.errorprone.annotations.Immutable;",
"class Test {{",
" new Super() {",
" // BUG: Diagnostic contains: non-final",
" int x = 0;",
" {",
" x++;",
" }",
" };",
"}}")
.doTest();
}
@Test
public void negativeParametricAnonymous() {
compilationHelper
.addSourceLines(
"threadsafety/Super.java",
"package threadsafety;",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable(containerOf=\"T\") class Super<T> {",
" private final T t = null;",
"}")
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import com.google.errorprone.annotations.Immutable;",
"class Test {",
" static <T> Super<T> get() {",
" return new Super<T>() {};",
" }",
"}")
.doTest();
}
@Test
public void interface_containerOf_immutable() {
compilationHelper
.addSourceLines(
"MyList.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable(containerOf=\"T\") public interface MyList<T> {",
" T get(int i);",
"}")
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"public class Test {",
" private final MyList<Integer> l = null;",
"}")
.doTest();
}
@Test
public void interface_containerOf_mutable() {
compilationHelper
.addSourceLines(
"MyList.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable(containerOf=\"T\") public interface MyList<T> {",
" T get(int i);",
"}")
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable public class Test<X> {",
" // BUG: Diagnostic contains: mutable type for 'T'",
" private final MyList<X> l = null;",
"}")
.doTest();
}
@Test
public void implementsInterface_containerOf() {
compilationHelper
.addSourceLines(
"MyList.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable(containerOf=\"T\") public interface MyList<T> {",
" T get(int i);",
"}")
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"// BUG: Diagnostic contains: 'X' is a mutable type",
"@Immutable public class Test<X> implements MyList<X> {",
" public X get(int i) { return null; }",
"}")
.doTest();
}
// sub-type tests
@Test
public void positive() {
compilationHelper
.addSourceLines(
"threadsafety/Super.java",
"package threadsafety;",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable class Super {",
"}")
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"// BUG: Diagnostic contains:",
"class Test extends Super {",
" public int x = 0;",
"}")
.doTest();
}
@Test
public void negative() {
compilationHelper
.addSourceLines(
"threadsafety/Super.java",
"package threadsafety;",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable class Super {",
"}")
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable class Test extends Super {",
"}")
.doTest();
}
// Report errors in compilation order, and detect transitive errors even if immediate
// supertype is unannotated.
@Test
public void transitive() {
compilationHelper
.addSourceLines(
"threadsafety/I.java",
"package threadsafety;",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable interface I {",
"}")
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"// BUG: Diagnostic contains: extends @Immutable",
"class Test implements J {",
" public int x = 0;",
"}")
.addSourceLines(
"threadsafety/J.java", "package threadsafety;", "interface J extends I {", "}")
.doTest();
}
// the type arguments are checked everywhere the super type is used
@Test
public void negativeAnonymousMutableBound() {
compilationHelper
.addSourceLines(
"threadsafety/Super.java",
"package threadsafety;",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable(containerOf=\"T\") class Super<T> {",
" private final T t = null;",
"}")
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import com.google.errorprone.annotations.Immutable;",
"class Test {{",
" new Super<Object>() {};",
"}}")
.doTest();
}
@Test
public void immutableAnonymousTypeScope() {
compilationHelper
.addSourceLines(
"threadsafety/Super.java",
"package threadsafety;",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable(containerOf=\"X\") class Super<X> {",
" private final X t = null;",
"}")
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable(containerOf=\"T\") class Test<T> {{",
" new Super<T>() {};",
"}}")
.doTest();
}
@Test
public void immutableClassSuperTypeScope() {
compilationHelper
.addSourceLines(
"threadsafety/Super.java",
"package threadsafety;",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable(containerOf=\"Y\") class Super<Y> {",
" @Immutable(containerOf=\"X\") class Inner1<X> {",
" private final X x = null;",
" private final Y y = null;",
" }",
"}")
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable(containerOf=\"U\") class Test<U> extends Super<U> {",
" @Immutable class Inner2 extends Inner1<U> {}",
"}")
.doTest();
}
@Test
public void immutableClassTypeScope() {
compilationHelper
.addSourceLines(
"threadsafety/Super.java",
"package threadsafety;",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable(containerOf=\"X\") class Super<X> {",
" private final X t = null;",
"}")
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable(containerOf=\"T\") class Test<T> {",
" @Immutable class Inner extends Super<T> {}",
"}")
.doTest();
}
@Test
public void negativeAnonymousBound() {
compilationHelper
.addSourceLines(
"threadsafety/Super.java",
"package threadsafety;",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable(containerOf=\"T\") class Super<T> {",
" private final T t = null;",
"}")
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import com.google.errorprone.annotations.Immutable;",
"class Test {{",
" new Super<String>() {};",
"}}")
.doTest();
}
@Test
public void negativeAnonymous() {
compilationHelper
.addSourceLines(
"threadsafety/Super.java",
"package threadsafety;",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable class Super {",
"}")
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import com.google.errorprone.annotations.Immutable;",
"class Test {{",
" new Super() {};",
"}}")
.doTest();
}
@Test
public void positiveEnumConstant() {
compilationHelper
.addSourceLines(
"threadsafety/Super.java",
"package threadsafety;",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable interface Super {",
" int f();",
"}")
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable enum Test implements Super {",
" INSTANCE {",
" // BUG: Diagnostic contains: non-final",
" public int x = 0;",
" public int f() {",
" return x++;",
" }",
" }",
"}")
.doTest();
}
@Test
public void negativeEnumConstant() {
compilationHelper
.addSourceLines(
"threadsafety/Super.java",
"package threadsafety;",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable interface Super {",
" void f();",
"}")
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable enum Test implements Super {",
" INSTANCE {",
" public void f() {",
" }",
" }",
"}")
.doTest();
}
// any final null reference constant is immutable, but do we actually care?
//
// javac makes it annoying to figure this out - since null isn't a compile-time constant,
// none of that machinery can be used. Instead, we need to look at the actual AST node
// for the member declaration to see that it's initialized to null.
@Ignore
@Test
public void immutableNull() {
compilationHelper
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable class Test {",
" final int[] xs = null;",
"}")
.doTest();
}
@Test
public void suppressOnField() {
compilationHelper
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable class Test {",
" @SuppressWarnings(\"Immutable\")",
" final int[] xs = {1};",
"}")
.doTest();
}
@Test
public void suppressOnOneField() {
compilationHelper
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable class Test {",
" @SuppressWarnings(\"Immutable\")",
" final int[] xs = {1};",
" // BUG: Diagnostic contains: arrays are mutable",
" final int[] ys = {1};",
"}")
.doTest();
}
@Test
public void twoFieldsInSource() {
compilationHelper
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable class Test {",
" // BUG: Diagnostic contains: arrays are mutable",
" final int[] xs = {1};",
" // BUG: Diagnostic contains: arrays are mutable",
" final int[] ys = {1};",
"}")
.doTest();
}
@Test
public void protosNotOnClasspath() {
compilationHelper
.addSourceLines(
"com/google/errorprone/annotations/Immutable.java",
"package com.google.errorprone.annotations;",
"import static java.lang.annotation.ElementType.TYPE;",
"import static java.lang.annotation.RetentionPolicy.RUNTIME;",
"import java.lang.annotation.Retention;",
"import java.lang.annotation.Target;",
"@Target(TYPE)",
"@Retention(RUNTIME)",
"public @interface Immutable {",
" String[] containerOf() default {};",
"}")
.addSourceLines("Foo.java", "class Foo {}")
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable class Test {",
" // BUG: Diagnostic contains: 'Foo' is not annotated @Immutable",
" final Foo f = null;",
"}")
.setArgs(Arrays.asList("-cp", "NOSUCH"))
.doTest();
}
@Ignore("b/25630186") // don't check enums for immutability yet
@Test
public void mutableEnum() {
compilationHelper
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"enum Test {",
" ;",
" // BUG: Diagnostic contains: @Immutable class has mutable field",
" private final Object o = null;",
"}")
.doTest();
}
@Ignore("b/25630186") // don't check enums for immutability yet
@Test
public void mutableEnumMember() {
compilationHelper
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"enum Test {",
" ONE {",
" // BUG: Diagnostic contains: @Immutable class has mutable field",
" private final Object o = null;",
" }",
"}")
.doTest();
}
@Ignore("b/25630189") // don't check annotations for immutability yet
@Test
public void mutableExtendsAnnotation() {
compilationHelper
.addSourceLines(
"Anno.java", //
"@interface Anno {}")
.addSourceLines(
"Test.java",
"abstract class Test implements Anno {",
" // BUG: Diagnostic contains: @Immutable class has mutable field",
" final Object o = null;",
"}")
.doTest();
}
@Test
public void mutableEnclosing() {
compilationHelper
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"public class Test {",
" int x = 0;",
" // BUG: Diagnostic contains: 'Inner' has mutable enclosing instance 'Test'",
" @Immutable public class Inner {",
" public int count() {",
" return x++;",
" }",
" }",
"}")
.doTest();
}
/** A sample superclass with a mutable field. */
public static class SuperFieldSuppressionTest {
@LazyInit public int x = 0;
public int count() {
return x++;
}
}
@Test
public void superFieldSuppression() {
compilationHelper
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"import " + SuperFieldSuppressionTest.class.getCanonicalName() + ";",
"@Immutable public class Test extends SuperFieldSuppressionTest {}")
.doTest();
}
@Test
public void rawClass() {
compilationHelper
.addSourceLines(
"Test.java",
"import com.google.errorprone.annotations.Immutable;",
"@Immutable class Test {",
" final Class clazz = Test.class;",
"}")
.doTest();
}
@Rule public final TemporaryFolder tempFolder = new TemporaryFolder();
/** Test class annotated with @Immutable. */
@Immutable(containerOf = {"T"})
public static class ClassPathTest<T> {}
static void addClassToJar(JarOutputStream jos, Class<?> clazz) throws IOException {
String entryPath = clazz.getName().replace('.', '/') + ".class";
try (InputStream is = clazz.getClassLoader().getResourceAsStream(entryPath)) {
jos.putNextEntry(new JarEntry(entryPath));
ByteStreams.copy(is, jos);
}
}
@Test
public void incompleteClassPath() throws Exception {
File libJar = tempFolder.newFile("lib.jar");
try (FileOutputStream fis = new FileOutputStream(libJar);
JarOutputStream jos = new JarOutputStream(fis)) {
addClassToJar(jos, ImmutableCheckerTest.ClassPathTest.class);
addClassToJar(jos, ImmutableCheckerTest.class);
}
compilationHelper
.addSourceLines(
"Test.java",
"import " + ClassPathTest.class.getCanonicalName() + ";",
"// BUG: Diagnostic contains: is not annotated",
"class Test extends ClassPathTest {",
"}")
.setArgs(Arrays.asList("-cp", libJar.toString()))
.doTest();
}
}