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