Skip to content

Commit e856722

Browse files
eamonnmcmanusGoogle Java Core Libraries
authored andcommitted
Recognize that <T extends @nullable Object> implies nullability.
If you have a method with a parameter of type `T`, we should not expect it to throw an exception when called with null. RELNOTES=In NullPointerTester, a parameter of type `<T extends @nullable Object>` is allowed to be null. PiperOrigin-RevId: 367051816
1 parent a6489b6 commit e856722

File tree

3 files changed

+85
-3
lines changed

3 files changed

+85
-3
lines changed

android/guava-testlib/src/com/google/common/testing/NullPointerTester.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
/**
5353
* A test utility that verifies that your methods and constructors throw {@link
5454
* NullPointerException} or {@link UnsupportedOperationException} whenever null is passed to a
55-
* parameter that isn't annotated with an annotation with the simple name {@code Nullable}, {@lcode
55+
* parameter that isn't annotated with an annotation with the simple name {@code Nullable}, {@code
5656
* CheckForNull}, {@link NullableType}, or {@link NullableDecl}.
5757
*
5858
* <p>The tested methods and constructors are invoked -- each time with one parameter being null and

guava-testlib/src/com/google/common/testing/NullPointerTester.java

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,19 @@
3434
import com.google.common.reflect.Reflection;
3535
import com.google.common.reflect.TypeToken;
3636
import java.lang.annotation.Annotation;
37+
import java.lang.reflect.AnnotatedType;
3738
import java.lang.reflect.Constructor;
3839
import java.lang.reflect.InvocationTargetException;
3940
import java.lang.reflect.Member;
4041
import java.lang.reflect.Method;
4142
import java.lang.reflect.Modifier;
4243
import java.lang.reflect.ParameterizedType;
4344
import java.lang.reflect.Type;
45+
import java.lang.reflect.TypeVariable;
4446
import java.util.Arrays;
4547
import java.util.List;
4648
import java.util.concurrent.ConcurrentMap;
49+
import java.util.function.Function;
4750
import junit.framework.Assert;
4851
import junit.framework.AssertionFailedError;
4952
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -52,7 +55,7 @@
5255
* A test utility that verifies that your methods and constructors throw {@link
5356
* NullPointerException} or {@link UnsupportedOperationException} whenever null is passed to a
5457
* parameter whose declaration or type isn't annotated with an annotation with the simple name
55-
* {@code Nullable}, {@lcode CheckForNull}, {@link NullableType}, or {@link NullableDecl}.
58+
* {@code Nullable}, {@code CheckForNull}, {@link NullableType}, or {@link NullableDecl}.
5659
*
5760
* <p>The tested methods and constructors are invoked -- each time with one parameter being null and
5861
* the rest not null -- and the test fails if no expected exception is thrown. {@code
@@ -483,7 +486,22 @@ static boolean isNullable(Invokable<?, ?> invokable) {
483486

484487
static boolean isNullable(Parameter param) {
485488
return isNullable(param.getAnnotatedType().getAnnotations())
486-
|| isNullable(param.getAnnotations());
489+
|| isNullable(param.getAnnotations())
490+
|| isNullableTypeVariable(param.getAnnotatedType().getType());
491+
}
492+
493+
private static boolean isNullableTypeVariable(Type type) {
494+
if (!(type instanceof TypeVariable)) {
495+
return false;
496+
}
497+
TypeVariable<?> var = (TypeVariable<?>) type;
498+
AnnotatedType[] bounds = GET_ANNOTATED_BOUNDS.apply(var);
499+
for (AnnotatedType bound : bounds) {
500+
if (isNullable(bound.getAnnotations()) || isNullableTypeVariable(bound.getType())) {
501+
return true;
502+
}
503+
}
504+
return false;
487505
}
488506

489507
private static boolean isNullable(Annotation[] annotations) {
@@ -495,6 +513,27 @@ private static boolean isNullable(Annotation[] annotations) {
495513
return false;
496514
}
497515

516+
// This is currently required because of j2objc restrictions.
517+
private static final Function<TypeVariable<?>, AnnotatedType[]> GET_ANNOTATED_BOUNDS =
518+
initGetAnnotatedBounds();
519+
520+
private static Function<TypeVariable<?>, AnnotatedType[]> initGetAnnotatedBounds() {
521+
AnnotatedType[] noBounds = new AnnotatedType[0];
522+
Method getAnnotatedBounds;
523+
try {
524+
getAnnotatedBounds = TypeVariable.class.getMethod("getAnnotatedBounds");
525+
} catch (ReflectiveOperationException e) {
526+
return v -> noBounds;
527+
}
528+
return v -> {
529+
try {
530+
return (AnnotatedType[]) getAnnotatedBounds.invoke(v);
531+
} catch (ReflectiveOperationException e) {
532+
return noBounds;
533+
}
534+
};
535+
}
536+
498537
private boolean isIgnored(Member member) {
499538
return member.isSynthetic() || ignoredMembers.contains(member) || isEquals(member);
500539
}

guava-testlib/test/com/google/common/testing/NullPointerTesterTest.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1484,4 +1484,47 @@ public void testConstructor_ShouldFail() throws Exception {
14841484
}
14851485
fail("Should detect problem in " + FailOnOneOfTwoConstructors.class.getSimpleName());
14861486
}
1487+
1488+
public static class NullBounds<T extends @Nullable Object, U extends T, X> {
1489+
boolean xWasCalled;
1490+
1491+
@SuppressWarnings("unused") // Called by reflection
1492+
public void x(X x) {
1493+
xWasCalled = true;
1494+
checkNotNull(x);
1495+
}
1496+
1497+
@SuppressWarnings("unused") // Called by reflection
1498+
public void t(T t) {
1499+
fail("Method with parameter <T extends @Nullable Object> should not be called");
1500+
}
1501+
1502+
@SuppressWarnings("unused") // Called by reflection
1503+
public void u(U u) {
1504+
fail(
1505+
"Method with parameter <U extends T> where <T extends @Nullable Object> should not be"
1506+
+ " called");
1507+
}
1508+
1509+
@SuppressWarnings("unused") // Called by reflection
1510+
public <A extends @Nullable Object> void a(A a) {
1511+
fail("Method with parameter <A extends @Nullable Object> should not be called");
1512+
}
1513+
1514+
@SuppressWarnings("unused") // Called by reflection
1515+
public <A extends B, B extends @Nullable Object> void b(A a) {
1516+
fail(
1517+
"Method with parameter <A extends B> where <B extends @Nullable Object> should not be"
1518+
+ " called");
1519+
}
1520+
}
1521+
1522+
public void testNullBounds() {
1523+
// NullBounds has methods whose parameters are type variables that have
1524+
// "extends @Nullable Object" as a bound. This test ensures that NullPointerTester considers
1525+
// those parameters to be @Nullable, so it won't call the methods.
1526+
NullBounds<?, ?, ?> nullBounds = new NullBounds<>();
1527+
new NullPointerTester().testAllPublicInstanceMethods(nullBounds);
1528+
assertThat(nullBounds.xWasCalled).isTrue();
1529+
}
14871530
}

0 commit comments

Comments
 (0)