001/*
002 * Copyright (C) 2005 The Guava Authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package com.google.common.testing;
018
019import static com.google.common.base.Preconditions.checkArgument;
020import static com.google.common.base.Preconditions.checkNotNull;
021import static java.util.Arrays.stream;
022import static java.util.Objects.requireNonNull;
023import static java.util.stream.Stream.concat;
024
025import com.google.common.annotations.GwtIncompatible;
026import com.google.common.annotations.J2ktIncompatible;
027import com.google.common.base.Converter;
028import com.google.common.base.Objects;
029import com.google.common.collect.ClassToInstanceMap;
030import com.google.common.collect.ImmutableList;
031import com.google.common.collect.ImmutableSet;
032import com.google.common.collect.Lists;
033import com.google.common.collect.Maps;
034import com.google.common.collect.MutableClassToInstanceMap;
035import com.google.common.reflect.Invokable;
036import com.google.common.reflect.Parameter;
037import com.google.common.reflect.Reflection;
038import com.google.common.reflect.TypeToken;
039import com.google.common.util.concurrent.AbstractFuture;
040import com.google.errorprone.annotations.CanIgnoreReturnValue;
041import java.lang.annotation.Annotation;
042import java.lang.reflect.AnnotatedType;
043import java.lang.reflect.Constructor;
044import java.lang.reflect.InvocationTargetException;
045import java.lang.reflect.Member;
046import java.lang.reflect.Method;
047import java.lang.reflect.Modifier;
048import java.lang.reflect.ParameterizedType;
049import java.lang.reflect.Type;
050import java.lang.reflect.TypeVariable;
051import java.util.Arrays;
052import java.util.List;
053import java.util.concurrent.ConcurrentMap;
054import junit.framework.Assert;
055import org.jspecify.annotations.NullMarked;
056import org.jspecify.annotations.Nullable;
057
058/**
059 * A test utility that verifies that your methods and constructors throw {@link
060 * NullPointerException} or {@link UnsupportedOperationException} whenever null is passed to a
061 * parameter whose declaration or type isn't annotated with an annotation with the simple name
062 * {@code Nullable}, {@code CheckForNull}, {@code NullableType}, or {@code NullableDecl}.
063 *
064 * <p>The tested methods and constructors are invoked -- each time with one parameter being null and
065 * the rest not null -- and the test fails if no expected exception is thrown. {@code
066 * NullPointerTester} uses best effort to pick non-null default values for many common JDK and Guava
067 * types, and also for interfaces and public classes that have public parameter-less constructors.
068 * When the non-null default value for a particular parameter type cannot be provided by {@code
069 * NullPointerTester}, the caller can provide a custom non-null default value for the parameter type
070 * via {@link #setDefault}.
071 *
072 * @author Kevin Bourrillion
073 * @since 10.0
074 */
075@GwtIncompatible
076@J2ktIncompatible
077@NullMarked
078public final class NullPointerTester {
079
080  private final ClassToInstanceMap<Object> defaults = MutableClassToInstanceMap.create();
081  private final List<Member> ignoredMembers = Lists.newArrayList();
082
083  private ExceptionTypePolicy policy = ExceptionTypePolicy.NPE_OR_UOE;
084
085  /*
086   * Requiring desugaring for guava-*testlib* is likely safe, at least for the reflection-based
087   * NullPointerTester. But if you are a user who is reading this because this change caused you
088   * trouble, please let us know: https://github.com/google/guava/issues/new
089   */
090  @IgnoreJRERequirement
091  public NullPointerTester() {
092    try {
093      /*
094       * Converter.apply has a non-nullable parameter type but doesn't throw for null arguments. For
095       * more information, see the comments in that class.
096       *
097       * We already know that that's how it behaves, and subclasses of Converter can't change that
098       * behavior. So there's no sense in making all subclass authors exclude the method from any
099       * NullPointerTester tests that they have.
100       */
101      ignoredMembers.add(Converter.class.getMethod("apply", Object.class));
102    } catch (NoSuchMethodException shouldBeImpossible) {
103      // Fine: If it doesn't exist, then there's no chance that we're going to be asked to test it.
104    }
105
106    /*
107     * These methods "should" call checkNotNull. However, I'm wary of accidentally introducing
108     * anything that might slow down execution on such a hot path. Given that the methods are only
109     * package-private, I feel OK with just not testing them for NPE.
110     *
111     * Note that testing casValue is particularly dangerous because it uses Unsafe under some
112     * versions of Java, and apparently Unsafe can cause SIGSEGV instead of NPE—almost as if it's
113     * not safe.
114     */
115    concat(
116            stream(AbstractFuture.class.getDeclaredMethods()),
117            stream(requireNonNull(AbstractFuture.class.getSuperclass()).getDeclaredMethods()))
118        .filter(
119            m ->
120                m.getName().equals("getDoneValue")
121                    || m.getName().equals("casValue")
122                    || m.getName().equals("casListeners")
123                    || m.getName().equals("gasListeners"))
124        .forEach(ignoredMembers::add);
125  }
126
127  /**
128   * Sets a default value that can be used for any parameter of type {@code type}. Returns this
129   * object.
130   */
131  @CanIgnoreReturnValue
132  public <T> NullPointerTester setDefault(Class<T> type, T value) {
133    defaults.putInstance(type, checkNotNull(value));
134    return this;
135  }
136
137  /**
138   * Ignore {@code method} in the tests that follow. Returns this object.
139   *
140   * @since 13.0
141   */
142  @CanIgnoreReturnValue
143  public NullPointerTester ignore(Method method) {
144    ignoredMembers.add(checkNotNull(method));
145    return this;
146  }
147
148  /**
149   * Ignore {@code constructor} in the tests that follow. Returns this object.
150   *
151   * @since 22.0
152   */
153  @CanIgnoreReturnValue
154  public NullPointerTester ignore(Constructor<?> constructor) {
155    ignoredMembers.add(checkNotNull(constructor));
156    return this;
157  }
158
159  /**
160   * Runs {@link #testConstructor} on every constructor in class {@code c} that has at least {@code
161   * minimalVisibility}.
162   */
163  public void testConstructors(Class<?> c, Visibility minimalVisibility) {
164    for (Constructor<?> constructor : c.getDeclaredConstructors()) {
165      if (minimalVisibility.isVisible(constructor) && !isIgnored(constructor)) {
166        testConstructor(constructor);
167      }
168    }
169  }
170
171  /** Runs {@link #testConstructor} on every public constructor in class {@code c}. */
172  public void testAllPublicConstructors(Class<?> c) {
173    testConstructors(c, Visibility.PUBLIC);
174  }
175
176  /**
177   * Runs {@link #testMethod} on every static method of class {@code c} that has at least {@code
178   * minimalVisibility}, including those "inherited" from superclasses of the same package.
179   */
180  public void testStaticMethods(Class<?> c, Visibility minimalVisibility) {
181    for (Method method : minimalVisibility.getStaticMethods(c)) {
182      if (!isIgnored(method)) {
183        testMethod(null, method);
184      }
185    }
186  }
187
188  /**
189   * Runs {@link #testMethod} on every public static method of class {@code c}, including those
190   * "inherited" from superclasses of the same package.
191   */
192  public void testAllPublicStaticMethods(Class<?> c) {
193    testStaticMethods(c, Visibility.PUBLIC);
194  }
195
196  /**
197   * Runs {@link #testMethod} on every instance method of the class of {@code instance} with at
198   * least {@code minimalVisibility}, including those inherited from superclasses of the same
199   * package.
200   */
201  public void testInstanceMethods(Object instance, Visibility minimalVisibility) {
202    for (Method method : getInstanceMethodsToTest(instance.getClass(), minimalVisibility)) {
203      testMethod(instance, method);
204    }
205  }
206
207  ImmutableList<Method> getInstanceMethodsToTest(Class<?> c, Visibility minimalVisibility) {
208    ImmutableList.Builder<Method> builder = ImmutableList.builder();
209    for (Method method : minimalVisibility.getInstanceMethods(c)) {
210      if (!isIgnored(method)) {
211        builder.add(method);
212      }
213    }
214    return builder.build();
215  }
216
217  /**
218   * Runs {@link #testMethod} on every public instance method of the class of {@code instance},
219   * including those inherited from superclasses of the same package.
220   */
221  public void testAllPublicInstanceMethods(Object instance) {
222    testInstanceMethods(instance, Visibility.PUBLIC);
223  }
224
225  /**
226   * Verifies that {@code method} produces a {@link NullPointerException} or {@link
227   * UnsupportedOperationException} whenever <i>any</i> of its non-nullable parameters are null.
228   *
229   * @param instance the instance to invoke {@code method} on, or null if {@code method} is static
230   */
231  public void testMethod(@Nullable Object instance, Method method) {
232    Class<?>[] types = method.getParameterTypes();
233    for (int nullIndex = 0; nullIndex < types.length; nullIndex++) {
234      testMethodParameter(instance, method, nullIndex);
235    }
236  }
237
238  /**
239   * Verifies that {@code ctor} produces a {@link NullPointerException} or {@link
240   * UnsupportedOperationException} whenever <i>any</i> of its non-nullable parameters are null.
241   */
242  public void testConstructor(Constructor<?> ctor) {
243    Class<?> declaringClass = ctor.getDeclaringClass();
244    checkArgument(
245        Modifier.isStatic(declaringClass.getModifiers())
246            || declaringClass.getEnclosingClass() == null,
247        "Cannot test constructor of non-static inner class: %s",
248        declaringClass.getName());
249    Class<?>[] types = ctor.getParameterTypes();
250    for (int nullIndex = 0; nullIndex < types.length; nullIndex++) {
251      testConstructorParameter(ctor, nullIndex);
252    }
253  }
254
255  /**
256   * Verifies that {@code method} produces a {@link NullPointerException} or {@link
257   * UnsupportedOperationException} when the parameter in position {@code paramIndex} is null. If
258   * this parameter is marked nullable, this method does nothing.
259   *
260   * @param instance the instance to invoke {@code method} on, or null if {@code method} is static
261   */
262  public void testMethodParameter(@Nullable Object instance, Method method, int paramIndex) {
263    method.setAccessible(true);
264    testParameter(instance, invokable(instance, method), paramIndex, method.getDeclaringClass());
265  }
266
267  /**
268   * Verifies that {@code ctor} produces a {@link NullPointerException} or {@link
269   * UnsupportedOperationException} when the parameter in position {@code paramIndex} is null. If
270   * this parameter is marked nullable, this method does nothing.
271   */
272  public void testConstructorParameter(Constructor<?> ctor, int paramIndex) {
273    ctor.setAccessible(true);
274    testParameter(null, Invokable.from(ctor), paramIndex, ctor.getDeclaringClass());
275  }
276
277  /** Visibility of any method or constructor. */
278  public enum Visibility {
279    PACKAGE {
280      @Override
281      boolean isVisible(int modifiers) {
282        return !Modifier.isPrivate(modifiers);
283      }
284    },
285
286    PROTECTED {
287      @Override
288      boolean isVisible(int modifiers) {
289        return Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers);
290      }
291    },
292
293    PUBLIC {
294      @Override
295      boolean isVisible(int modifiers) {
296        return Modifier.isPublic(modifiers);
297      }
298    };
299
300    abstract boolean isVisible(int modifiers);
301
302    /** Returns {@code true} if {@code member} is visible under {@code this} visibility. */
303    final boolean isVisible(Member member) {
304      return isVisible(member.getModifiers());
305    }
306
307    final Iterable<Method> getStaticMethods(Class<?> cls) {
308      ImmutableList.Builder<Method> builder = ImmutableList.builder();
309      for (Method method : getVisibleMethods(cls)) {
310        if (Invokable.from(method).isStatic()) {
311          builder.add(method);
312        }
313      }
314      return builder.build();
315    }
316
317    final Iterable<Method> getInstanceMethods(Class<?> cls) {
318      ConcurrentMap<Signature, Method> map = Maps.newConcurrentMap();
319      for (Method method : getVisibleMethods(cls)) {
320        if (!Invokable.from(method).isStatic()) {
321          map.putIfAbsent(new Signature(method), method);
322        }
323      }
324      return map.values();
325    }
326
327    private ImmutableList<Method> getVisibleMethods(Class<?> cls) {
328      // Don't use cls.getPackage() because it does nasty things like reading
329      // a file.
330      String visiblePackage = Reflection.getPackageName(cls);
331      ImmutableList.Builder<Method> builder = ImmutableList.builder();
332      for (Class<?> type : TypeToken.of(cls).getTypes().rawTypes()) {
333        if (!Reflection.getPackageName(type).equals(visiblePackage)) {
334          break;
335        }
336        for (Method method : type.getDeclaredMethods()) {
337          if (!method.isSynthetic() && isVisible(method)) {
338            builder.add(method);
339          }
340        }
341      }
342      return builder.build();
343    }
344  }
345
346  private static final class Signature {
347    private final String name;
348    private final ImmutableList<Class<?>> parameterTypes;
349
350    Signature(Method method) {
351      this(method.getName(), ImmutableList.copyOf(method.getParameterTypes()));
352    }
353
354    Signature(String name, ImmutableList<Class<?>> parameterTypes) {
355      this.name = name;
356      this.parameterTypes = parameterTypes;
357    }
358
359    @Override
360    public boolean equals(@Nullable Object obj) {
361      if (obj instanceof Signature) {
362        Signature that = (Signature) obj;
363        return name.equals(that.name) && parameterTypes.equals(that.parameterTypes);
364      }
365      return false;
366    }
367
368    @Override
369    public int hashCode() {
370      return Objects.hashCode(name, parameterTypes);
371    }
372  }
373
374  /**
375   * Verifies that {@code invokable} produces a {@link NullPointerException} or {@link
376   * UnsupportedOperationException} when the parameter in position {@code paramIndex} is null. If
377   * this parameter is marked nullable, this method does nothing.
378   *
379   * @param instance the instance to invoke {@code invokable} on, or null if {@code invokable} is
380   *     static
381   */
382  private void testParameter(
383      @Nullable Object instance, Invokable<?, ?> invokable, int paramIndex, Class<?> testedClass) {
384    if (isPrimitiveOrNullable(invokable.getParameters().get(paramIndex))) {
385      return; // there's nothing to test
386    }
387    @Nullable Object[] params = buildParamList(invokable, paramIndex);
388    try {
389      @SuppressWarnings("unchecked") // We'll get a runtime exception if the type is wrong.
390      Invokable<Object, ?> unsafe = (Invokable<Object, ?>) invokable;
391      unsafe.invoke(instance, params);
392      Assert.fail(
393          "No exception thrown for parameter at index "
394              + paramIndex
395              + " from "
396              + invokable
397              + Arrays.toString(params)
398              + " for "
399              + testedClass);
400    } catch (InvocationTargetException e) {
401      Throwable cause = e.getCause();
402      if (policy.isExpectedType(cause)) {
403        return;
404      }
405      throw new AssertionError(
406          String.format(
407              "wrong exception thrown from %s when passing null to %s parameter at index %s.%n"
408                  + "Full parameters: %s%n"
409                  + "Actual exception message: %s",
410              invokable,
411              invokable.getParameters().get(paramIndex).getType(),
412              paramIndex,
413              Arrays.toString(params),
414              cause),
415          cause);
416    } catch (IllegalAccessException e) {
417      throw new RuntimeException(e);
418    }
419  }
420
421  private @Nullable Object[] buildParamList(
422      Invokable<?, ?> invokable, int indexOfParamToSetToNull) {
423    ImmutableList<Parameter> params = invokable.getParameters();
424    @Nullable Object[] args = new Object[params.size()];
425
426    for (int i = 0; i < args.length; i++) {
427      Parameter param = params.get(i);
428      if (i != indexOfParamToSetToNull) {
429        args[i] = getDefaultValue(param.getType());
430        Assert.assertTrue(
431            "Can't find or create a sample instance for type '"
432                + param.getType()
433                + "'; please provide one using NullPointerTester.setDefault()",
434            args[i] != null || isNullable(param));
435      }
436    }
437    return args;
438  }
439
440  private <T> @Nullable T getDefaultValue(TypeToken<T> type) {
441    // We assume that all defaults are generics-safe, even if they aren't,
442    // we take the risk.
443    @SuppressWarnings("unchecked")
444    T defaultValue = (T) defaults.getInstance(type.getRawType());
445    if (defaultValue != null) {
446      return defaultValue;
447    }
448    @SuppressWarnings("unchecked") // All arbitrary instances are generics-safe
449    T arbitrary = (T) ArbitraryInstances.get(type.getRawType());
450    if (arbitrary != null) {
451      return arbitrary;
452    }
453    if (type.getRawType() == Class.class) {
454      // If parameter is Class<? extends Foo>, we return Foo.class
455      @SuppressWarnings("unchecked")
456      T defaultClass = (T) getFirstTypeParameter(type.getType()).getRawType();
457      return defaultClass;
458    }
459    if (type.getRawType() == TypeToken.class) {
460      // If parameter is TypeToken<? extends Foo>, we return TypeToken<Foo>.
461      @SuppressWarnings("unchecked")
462      T defaultType = (T) getFirstTypeParameter(type.getType());
463      return defaultType;
464    }
465    if (type.getRawType() == Converter.class) {
466      TypeToken<?> convertFromType = type.resolveType(Converter.class.getTypeParameters()[0]);
467      TypeToken<?> convertToType = type.resolveType(Converter.class.getTypeParameters()[1]);
468      @SuppressWarnings("unchecked") // returns default for both F and T
469      T defaultConverter = (T) defaultConverter(convertFromType, convertToType);
470      return defaultConverter;
471    }
472    if (type.getRawType().isInterface()) {
473      return newDefaultReturningProxy(type);
474    }
475    return null;
476  }
477
478  private <F, T> Converter<F, T> defaultConverter(
479      TypeToken<F> convertFromType, TypeToken<T> convertToType) {
480    return new Converter<F, T>() {
481      @Override
482      protected T doForward(F a) {
483        return doConvert(convertToType);
484      }
485
486      @Override
487      protected F doBackward(T b) {
488        return doConvert(convertFromType);
489      }
490
491      private /*static*/ <S> S doConvert(TypeToken<S> type) {
492        return checkNotNull(getDefaultValue(type));
493      }
494    };
495  }
496
497  private static TypeToken<?> getFirstTypeParameter(Type type) {
498    if (type instanceof ParameterizedType) {
499      return TypeToken.of(((ParameterizedType) type).getActualTypeArguments()[0]);
500    } else {
501      return TypeToken.of(Object.class);
502    }
503  }
504
505  private <T> T newDefaultReturningProxy(TypeToken<T> type) {
506    return new DummyProxy() {
507      @Override
508      <R> @Nullable R dummyReturnValue(TypeToken<R> returnType) {
509        return getDefaultValue(returnType);
510      }
511    }.newProxy(type);
512  }
513
514  private static Invokable<?, ?> invokable(@Nullable Object instance, Method method) {
515    if (instance == null) {
516      return Invokable.from(method);
517    } else {
518      return TypeToken.of(instance.getClass()).method(method);
519    }
520  }
521
522  static boolean isPrimitiveOrNullable(Parameter param) {
523    return param.getType().getRawType().isPrimitive() || isNullable(param);
524  }
525
526  private static final ImmutableSet<String> NULLABLE_ANNOTATION_SIMPLE_NAMES =
527      ImmutableSet.of("CheckForNull", "Nullable", "NullableDecl", "NullableType");
528
529  static boolean isNullable(Invokable<?, ?> invokable) {
530    return NULLNESS_ANNOTATION_READER.isNullable(invokable);
531  }
532
533  static boolean isNullable(Parameter param) {
534    return NULLNESS_ANNOTATION_READER.isNullable(param);
535  }
536
537  private static boolean containsNullable(Annotation[] annotations) {
538    for (Annotation annotation : annotations) {
539      if (NULLABLE_ANNOTATION_SIMPLE_NAMES.contains(annotation.annotationType().getSimpleName())) {
540        return true;
541      }
542    }
543    return false;
544  }
545
546  private boolean isIgnored(Member member) {
547    return member.isSynthetic() || ignoredMembers.contains(member) || isEquals(member);
548  }
549
550  /**
551   * Returns true if the given member is a method that overrides {@link Object#equals(Object)}.
552   *
553   * <p>The documentation for {@link Object#equals} says it should accept null, so don't require an
554   * explicit {@code @Nullable} annotation (see <a
555   * href="https://github.com/google/guava/issues/1819">#1819</a>).
556   *
557   * <p>It is not necessary to consider visibility, return type, or type parameter declarations. The
558   * declaration of a method with the same name and formal parameters as {@link Object#equals} that
559   * is not public and boolean-returning, or that declares any type parameters, would be rejected at
560   * compile-time.
561   */
562  private static boolean isEquals(Member member) {
563    if (!(member instanceof Method)) {
564      return false;
565    }
566    Method method = (Method) member;
567    if (!method.getName().contentEquals("equals")) {
568      return false;
569    }
570    Class<?>[] parameters = method.getParameterTypes();
571    if (parameters.length != 1) {
572      return false;
573    }
574    if (!parameters[0].equals(Object.class)) {
575      return false;
576    }
577    return true;
578  }
579
580  /** Strategy for exception type matching used by {@link NullPointerTester}. */
581  private enum ExceptionTypePolicy {
582
583    /**
584     * Exceptions should be {@link NullPointerException} or {@link UnsupportedOperationException}.
585     */
586    NPE_OR_UOE() {
587      @Override
588      public boolean isExpectedType(Throwable cause) {
589        return cause instanceof NullPointerException
590            || cause instanceof UnsupportedOperationException;
591      }
592    },
593
594    /**
595     * Exceptions should be {@link NullPointerException}, {@link IllegalArgumentException}, or
596     * {@link UnsupportedOperationException}.
597     */
598    NPE_IAE_OR_UOE() {
599      @Override
600      public boolean isExpectedType(Throwable cause) {
601        return cause instanceof NullPointerException
602            || cause instanceof IllegalArgumentException
603            || cause instanceof UnsupportedOperationException;
604      }
605    };
606
607    public abstract boolean isExpectedType(Throwable cause);
608  }
609
610  private static boolean annotatedTypeExists() {
611    try {
612      Class.forName("java.lang.reflect.AnnotatedType");
613    } catch (ClassNotFoundException e) {
614      return false;
615    }
616    return true;
617  }
618
619  private static final NullnessAnnotationReader NULLNESS_ANNOTATION_READER =
620      annotatedTypeExists()
621          ? NullnessAnnotationReader.FROM_DECLARATION_AND_TYPE_USE_ANNOTATIONS
622          : NullnessAnnotationReader.FROM_DECLARATION_ANNOTATIONS_ONLY;
623
624  /**
625   * Looks for declaration nullness annotations and, if supported, type-use nullness annotations.
626   *
627   * <p>Under Android VMs, the methods for retrieving type-use annotations don't exist. This means
628   * that {@link NullPointerTester} may misbehave under Android when used on classes that rely on
629   * type-use annotations.
630   *
631   * <p>Under j2objc, the necessary APIs exist, but some (perhaps all) return stub values, like
632   * empty arrays. Presumably {@link NullPointerTester} could likewise misbehave under j2objc, but I
633   * don't know that anyone uses it there, anyway.
634   */
635  private enum NullnessAnnotationReader {
636    @SuppressWarnings("Java7ApiChecker")
637    FROM_DECLARATION_AND_TYPE_USE_ANNOTATIONS {
638      @Override
639      boolean isNullable(Invokable<?, ?> invokable) {
640        return FROM_DECLARATION_ANNOTATIONS_ONLY.isNullable(invokable)
641            || containsNullable(invokable.getAnnotatedReturnType().getAnnotations());
642        // TODO(cpovirk): Should we also check isNullableTypeVariable?
643      }
644
645      @Override
646      boolean isNullable(Parameter param) {
647        return FROM_DECLARATION_ANNOTATIONS_ONLY.isNullable(param)
648            || containsNullable(param.getAnnotatedType().getAnnotations())
649            || isNullableTypeVariable(param.getAnnotatedType().getType());
650      }
651
652      boolean isNullableTypeVariable(Type type) {
653        if (!(type instanceof TypeVariable)) {
654          return false;
655        }
656        TypeVariable<?> typeVar = (TypeVariable<?>) type;
657        for (AnnotatedType bound : typeVar.getAnnotatedBounds()) {
658          // Until Java 15, the isNullableTypeVariable case here won't help:
659          // https://bugs.openjdk.org/browse/JDK-8202469
660          if (containsNullable(bound.getAnnotations()) || isNullableTypeVariable(bound.getType())) {
661            return true;
662          }
663        }
664        return false;
665      }
666    },
667    FROM_DECLARATION_ANNOTATIONS_ONLY {
668      @Override
669      boolean isNullable(Invokable<?, ?> invokable) {
670        return containsNullable(invokable.getAnnotations());
671      }
672
673      @Override
674      boolean isNullable(Parameter param) {
675        return containsNullable(param.getAnnotations());
676      }
677    };
678
679    abstract boolean isNullable(Invokable<?, ?> invokable);
680
681    abstract boolean isNullable(Parameter param);
682  }
683}