1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.tools.metalava.lint 18 19 import com.android.tools.metalava.DriverTest 20 import com.android.tools.metalava.androidxNonNullSource 21 import com.android.tools.metalava.androidxNullableSource 22 import com.android.tools.metalava.cli.common.ARG_ERROR 23 import com.android.tools.metalava.cli.common.ARG_HIDE 24 import com.android.tools.metalava.cli.lint.ARG_API_LINT 25 import com.android.tools.metalava.libcoreNonNullSource 26 import com.android.tools.metalava.libcoreNullableSource 27 import com.android.tools.metalava.model.provider.Capability 28 import com.android.tools.metalava.model.testing.RequiresCapabilities 29 import com.android.tools.metalava.testing.java 30 import com.android.tools.metalava.testing.kotlin 31 import org.junit.Test 32 33 class NullabilityLintTest : DriverTest() { 34 @Test Test fields, parameters and returns require nullabilitynull35 fun `Test fields, parameters and returns require nullability`() { 36 check( 37 apiLint = "", // enabled 38 expectedIssues = 39 """ 40 src/android/pkg/Foo.java:11: error: Missing nullability on parameter `name` in method `Foo` [MissingNullability] 41 src/android/pkg/Foo.java:12: error: Missing nullability on parameter `value` in method `setBadValue` [MissingNullability] 42 src/android/pkg/Foo.java:13: error: Missing nullability on method `getBadValue` return [MissingNullability] 43 src/android/pkg/Foo.java:20: error: Missing nullability on parameter `duration` in method `methodMissingParamAnnotations` [MissingNullability] 44 src/android/pkg/Foo.java:7: error: Missing nullability on field `badField` in class `class android.pkg.Foo` [MissingNullability] 45 """, 46 expectedFail = DefaultLintErrorMessage, 47 sourceFiles = 48 arrayOf( 49 java( 50 """ 51 package android.pkg; 52 53 import androidx.annotation.NonNull; 54 import androidx.annotation.Nullable; 55 56 public class Foo<T> { 57 public final Foo badField; 58 @Nullable 59 public final Foo goodField; 60 61 public Foo(String name, int number) { } 62 public void setBadValue(Foo value) { } 63 public Foo getBadValue(int number) { throw UnsupportedOperationExceptions(); } 64 public void setGoodValue(@Nullable Foo value) { } 65 public void setGoodIgnoredGenericValue(T value) { } 66 @NonNull 67 public Foo getGoodValue(int number) { throw UnsupportedOperationExceptions(); } 68 69 @NonNull 70 public Foo methodMissingParamAnnotations(java.time.Duration duration) { 71 throw UnsupportedOperationException(); 72 } 73 } 74 """ 75 ), 76 androidxNullableSource, 77 androidxNonNullSource 78 ) 79 ) 80 } 81 82 @RequiresCapabilities(Capability.KOTLIN) 83 @Test Test no missing nullability errors for enumsnull84 fun `Test no missing nullability errors for enums`() { 85 check( 86 apiLint = "", // enabled 87 extraArguments = arrayOf(ARG_HIDE, "Enum"), 88 sourceFiles = 89 arrayOf( 90 java( 91 """ 92 package test.pkg; 93 @SuppressWarnings("ALL") 94 public enum Foo { 95 A, B; 96 } 97 """ 98 ), 99 kotlin( 100 """ 101 package test.pkg 102 enum class Language { 103 KOTLIN, 104 JAVA 105 } 106 """ 107 ), 108 ) 109 ) 110 } 111 112 @RequiresCapabilities(Capability.KOTLIN) 113 @Test Test no missing nullability errors for kotlin constructsnull114 fun `Test no missing nullability errors for kotlin constructs`() { 115 check( 116 apiLint = "", // enabled 117 extraArguments = arrayOf(ARG_HIDE, "StaticUtils"), 118 sourceFiles = 119 arrayOf( 120 kotlin( 121 """ 122 package android.pkg 123 124 object Bar 125 126 class FooBar { 127 companion object 128 } 129 130 class FooBarNamed { 131 companion object Named 132 } 133 134 class Foo { 135 val a = 3 136 var b: String? = null 137 } 138 """ 139 ), 140 ), 141 ) 142 } 143 144 @Test Test type variable array requires nullabilitynull145 fun `Test type variable array requires nullability`() { 146 check( 147 apiLint = "", // enabled 148 extraArguments = arrayOf(ARG_API_LINT, ARG_HIDE, "ArrayReturn"), 149 expectedIssues = 150 """ 151 src/test/pkg/Foo.java:4: error: Missing nullability on method `badTypeVarArrayReturn` return [MissingNullability] 152 """, 153 expectedFail = DefaultLintErrorMessage, 154 sourceFiles = 155 arrayOf( 156 java( 157 """ 158 package test.pkg; 159 public class Foo<T> { 160 public T goodTypeVarReturn() { return null; } 161 public T[] badTypeVarArrayReturn() { return null; } 162 } 163 """ 164 .trimIndent() 165 ) 166 ) 167 ) 168 } 169 170 @Test Test equals, toString, non-null constants, enums and annotation members don't require nullabilitynull171 fun `Test equals, toString, non-null constants, enums and annotation members don't require nullability`() { 172 check( 173 apiLint = "", // enabled 174 expectedIssues = "", 175 sourceFiles = 176 arrayOf( 177 java( 178 """ 179 package android.pkg; 180 181 import android.annotation.SuppressLint; 182 183 public class Foo<T> { 184 public static final String FOO_CONSTANT = "test"; 185 186 public boolean equals(Object other) { 187 return other == this; 188 } 189 190 public int hashCode() { 191 return 0; 192 } 193 194 public String toString() { 195 return "Foo"; 196 } 197 198 @SuppressLint("Enum") 199 public enum FooEnum { 200 FOO, BAR 201 } 202 203 public @interface FooAnnotation { 204 String value() default ""; 205 } 206 } 207 """ 208 ), 209 androidxNullableSource, 210 androidxNonNullSource 211 ) 212 ) 213 } 214 215 @Test 216 fun `Nullability check for generic methods referencing parent type parameter`() { 217 check( 218 apiLint = "", // enabled 219 expectedIssues = 220 """ 221 src/test/pkg/MyClass.java:14: error: Missing nullability on method `method4` return [MissingNullability] 222 src/test/pkg/MyClass.java:14: error: Missing nullability on parameter `input` in method `method4` [MissingNullability] 223 """, 224 expectedFail = DefaultLintErrorMessage, 225 sourceFiles = 226 arrayOf( 227 java( 228 """ 229 package test.pkg; 230 231 import androidx.annotation.NonNull; 232 import androidx.annotation.Nullable; 233 234 public class MyClass extends HiddenParent<String> { 235 public void method1() { } 236 237 @NonNull 238 @Override 239 public String method3(@Nullable String input) { return null; } 240 241 @Override 242 public String method4(String input) { return null; } 243 } 244 """ 245 ), 246 java( 247 """ 248 package test.pkg; 249 250 class HiddenParent<T> { 251 public T method2(T t) { } 252 public T method3(T t) { } 253 public T method4(T t) { } 254 } 255 """ 256 ), 257 androidxNullableSource, 258 androidxNonNullSource 259 ) 260 ) 261 } 262 263 @RequiresCapabilities(Capability.KOTLIN) 264 @Test 265 fun `No warnings about nullability on private constructor getters`() { 266 check( 267 expectedIssues = "", 268 apiLint = "", 269 sourceFiles = 270 arrayOf( 271 kotlin( 272 """ 273 package test.pkg 274 class MyClass private constructor( 275 val myParameter: Set<Int> 276 ) 277 """ 278 ) 279 ) 280 ) 281 } 282 283 @RequiresCapabilities(Capability.KOTLIN) 284 @Test 285 fun `No error for nullability on synthetic methods`() { 286 check( 287 expectedIssues = "", 288 apiLint = "", 289 sourceFiles = 290 arrayOf( 291 kotlin( 292 """ 293 package test.pkg 294 class Foo { 295 @JvmSynthetic 296 fun bar(): String {} 297 } 298 """ 299 ) 300 ) 301 ) 302 } 303 304 @Test 305 fun `Constructors return types don't require nullability`() { 306 check( 307 expectedIssues = "", 308 apiLint = "", 309 sourceFiles = 310 arrayOf( 311 java( 312 """ 313 package test.pkg; 314 import androidx.annotation.NonNull; 315 public class Foo { 316 // Doesn't require nullability 317 public Foo(@NonNull String bar); 318 // Requires nullability 319 public @NonNull String baz(@NonNull String whatever); 320 } 321 """ 322 ), 323 androidxNonNullSource 324 ) 325 ) 326 } 327 328 @Test No nullability allowed on overrides of unannotated methods or parametersnull329 fun `No nullability allowed on overrides of unannotated methods or parameters`() { 330 check( 331 expectedIssues = 332 """ 333 src/test/pkg/Foo.java:16: error: Invalid nullability on type java.lang.String in method `bar` return. Method override cannot use a nullable type when the corresponding type from the super method is platform-nullness. [InvalidNullabilityOverride] 334 src/test/pkg/Foo.java:16: error: Invalid nullability on type java.lang.String in parameter `baz` in method `bar`. Parameter in method override cannot use a non-null type when the corresponding type from the super method is platform-nullness. [InvalidNullabilityOverride] 335 src/test/pkg/Foo.java:19: error: Invalid nullability on type java.lang.String in parameter `y` in method `x`. Parameter in method override cannot use a non-null type when the corresponding type from the super method is platform-nullness. [InvalidNullabilityOverride] 336 src/test/pkg/Foo.java:8: error: Missing nullability on method `bar` return [MissingNullability] 337 src/test/pkg/Foo.java:8: error: Missing nullability on parameter `baz` in method `bar` [MissingNullability] 338 src/test/pkg/Foo.java:11: error: Missing nullability on parameter `y` in method `x` [MissingNullability] 339 """, 340 apiLint = "", 341 expectedFail = DefaultLintErrorMessage, 342 sourceFiles = 343 arrayOf( 344 java( 345 """ 346 package test.pkg; 347 348 import androidx.annotation.NonNull; 349 import androidx.annotation.Nullable; 350 351 public class Foo { 352 // Not annotated 353 public String bar(String baz); 354 355 // Partially annotated 356 @Nullable public String x(String y); 357 } 358 public class Bar extends Foo { 359 // Not allowed to mark override method Nullable if parent is not annotated 360 // Not allowed to mark override parameter NonNull if parent is not annotated 361 @Nullable @Override public String bar(@NonNull String baz); 362 // It is allowed to mark the override method Nullable if the parent is Nullable. 363 // Not allowed to mark override parameter if parent is not annotated. 364 @Nullable @Override public String x(@NonNull String y); 365 } 366 """ 367 ), 368 androidxNullableSource, 369 androidxNonNullSource 370 ) 371 ) 372 } 373 374 @RequiresCapabilities(Capability.KOTLIN) 375 @Test Override enforcement on kotlin sourced child classnull376 fun `Override enforcement on kotlin sourced child class`() { 377 check( 378 expectedIssues = 379 """ 380 src/test/pkg/Bar.kt:5: error: Invalid nullability on type java.lang.String in parameter `baz` in method `bar`. Parameter in method override cannot use a non-null type when the corresponding type from the super method is platform-nullness. [InvalidNullabilityOverride] 381 src/test/pkg/Foo.java:5: error: Missing nullability on method `bar` return [MissingNullability] 382 src/test/pkg/Foo.java:5: error: Missing nullability on parameter `baz` in method `bar` [MissingNullability] 383 """, 384 apiLint = "", 385 expectedFail = DefaultLintErrorMessage, 386 sourceFiles = 387 arrayOf( 388 java( 389 """ 390 package test.pkg; 391 392 public class Foo { 393 // Not annotated 394 public String bar(String baz); 395 } 396 """ 397 ), 398 kotlin( 399 """ 400 package test.pkg 401 // Not allowed to mark override method Nullable if parent is not annotated 402 // Not allowed to mark override parameter NonNull if parent is not annotated 403 class Bar : Foo { 404 override fun bar(baz: String): String 405 } 406 """ 407 ), 408 androidxNullableSource, 409 androidxNonNullSource 410 ) 411 ) 412 } 413 414 @Test Overrides of non-null methods cannot be nullablenull415 fun `Overrides of non-null methods cannot be nullable`() { 416 check( 417 expectedIssues = 418 """ 419 src/test/pkg/Foo.java:12: error: Invalid nullability on type java.lang.String method `bar` return. Method override cannot use a nullable type when the corresponding type from the super method is non-null. [InvalidNullabilityOverride] 420 """, 421 apiLint = "", 422 expectedFail = DefaultLintErrorMessage, 423 sourceFiles = 424 arrayOf( 425 java( 426 """ 427 package test.pkg; 428 429 import androidx.annotation.NonNull; 430 import androidx.annotation.Nullable; 431 432 public class Foo { 433 @NonNull public String bar(@Nullable String baz); 434 } 435 436 // Not allowed to mark override method Nullable if parent is nonNull 437 public class Bar extends Foo { 438 @Nullable @Override public String bar(@Nullable String baz); 439 } 440 """ 441 ), 442 androidxNullableSource, 443 androidxNonNullSource 444 ) 445 ) 446 } 447 448 @Test Overrides of nullable parameters cannot be non-nullnull449 fun `Overrides of nullable parameters cannot be non-null`() { 450 check( 451 expectedIssues = 452 """ 453 src/test/pkg/Foo.java:13: error: Invalid nullability on type java.lang.String in parameter `baz` in method `bar`. Parameter in method override cannot use a non-null type when the corresponding type from the super method is nullable. [InvalidNullabilityOverride] 454 """, 455 apiLint = "", 456 expectedFail = DefaultLintErrorMessage, 457 sourceFiles = 458 arrayOf( 459 java( 460 """ 461 package test.pkg; 462 463 import androidx.annotation.NonNull; 464 import androidx.annotation.Nullable; 465 466 public class Foo { 467 // Not annotated 468 @NonNull public String bar(@Nullable String baz); 469 } 470 471 // Not allowed to mark override parameter NonNull if parent is Nullable 472 public class Bar extends Foo { 473 @NonNull @Override public String bar(@NonNull String baz); 474 } 475 """ 476 ), 477 androidxNullableSource, 478 androidxNonNullSource 479 ) 480 ) 481 } 482 483 @RequiresCapabilities(Capability.KOTLIN) 484 @Test Nullability overrides in unbounded generics should be allowednull485 fun `Nullability overrides in unbounded generics should be allowed`() { 486 check( 487 apiLint = "", 488 sourceFiles = 489 arrayOf( 490 kotlin( 491 """ 492 package test.pkg; 493 494 interface Base<T> { 495 fun method1(input: T): T 496 } 497 498 class Subject1 : Base<String> { 499 override fun method1(input: String): String { 500 TODO() 501 } 502 } 503 504 class Subject2 : Base<String?> { 505 override fun method1(input: String?): String? { 506 TODO() 507 } 508 } 509 """ 510 ) 511 ) 512 ) 513 } 514 515 @Test Invalid nullability override in function with generic parameternull516 fun `Invalid nullability override in function with generic parameter`() { 517 check( 518 apiLint = "", 519 expectedFail = DefaultLintErrorMessage, 520 expectedIssues = 521 "src/test/pkg/StringProperty.java:5: error: Invalid nullability on type java.lang.String in parameter `arg2` in method `foo`. Parameter in method override cannot use a non-null type when the corresponding type from the super method is platform-nullness. [InvalidNullabilityOverride]", 522 sourceFiles = 523 arrayOf( 524 java( 525 """ 526 package test.pkg; 527 public abstract class Property<V> { 528 public abstract void foo(V arg1, @SuppressWarnings("MissingNullability") String arg2); 529 } 530 """ 531 ), 532 java( 533 """ 534 package test.pkg; 535 import androidx.annotation.NonNull; 536 public class StringProperty extends Property<String> { 537 @Override 538 public void foo(@NonNull String arg1, @NonNull String arg2) {} 539 } 540 """ 541 ), 542 androidxNonNullSource, 543 ) 544 ) 545 } 546 547 @RequiresCapabilities(Capability.KOTLIN) 548 @Test Nullability overrides in unbounded generics (Object to generic and back)null549 fun `Nullability overrides in unbounded generics (Object to generic and back)`() { 550 check( 551 apiLint = "", 552 expectedFail = DefaultLintErrorMessage, 553 expectedIssues = 554 "src/test/pkg/ArrayMap.java:11: error: Invalid nullability on type java.lang.Object in parameter `key` in method `get`. Parameter in method override cannot use a non-null type when the corresponding type from the super method is nullable. [InvalidNullabilityOverride]", 555 sourceFiles = 556 arrayOf( 557 kotlin( 558 """ 559 package test.pkg 560 561 open class SimpleArrayMap<K, V> { 562 open operator fun get(key: K): V? { 563 TODO() 564 } 565 } 566 """ 567 ), 568 java( 569 """ 570 package test.pkg; 571 572 import java.util.Map; 573 import androidx.annotation.NonNull; 574 import androidx.annotation.Nullable; 575 576 // The android version of [Map.get] has a @Nullable parameter 577 public class ArrayMap<K, V> extends SimpleArrayMap<K, V> implements Map<K, V> { 578 @Override 579 @Nullable 580 public V get(@NonNull Object key) { 581 return super.get((K) key); 582 } 583 } 584 585 """ 586 ), 587 androidxNonNullSource, 588 androidxNullableSource, 589 ) 590 ) 591 } 592 593 @RequiresCapabilities(Capability.KOTLIN) 594 @Test Nullability overrides in unbounded generics (one super method lacks nullness info)null595 fun `Nullability overrides in unbounded generics (one super method lacks nullness info)`() { 596 check( 597 apiLint = "", 598 sourceFiles = 599 arrayOf( 600 kotlin( 601 """ 602 package test.pkg 603 604 open class SimpleArrayMap<K, V> { 605 open operator fun get(key: K): V? { 606 TODO() 607 } 608 } 609 """ 610 ), 611 java( 612 """ 613 package test.pkg; 614 615 import java.util.Map; 616 import androidx.annotation.Nullable; 617 618 // The android version of [Map.get] has a @Nullable parameter 619 public class ArrayMap<K, V> extends SimpleArrayMap<K, V> implements Map<K, V> { 620 @Override 621 @Nullable 622 public V get(@Nullable Object key) { 623 return super.get((K) key); 624 } 625 } 626 """ 627 ), 628 androidxNullableSource 629 ) 630 ) 631 } 632 633 @RequiresCapabilities(Capability.KOTLIN) 634 @Test Nullability on vararg with inherited generic typenull635 fun `Nullability on vararg with inherited generic type`() { 636 check( 637 apiLint = "", 638 sourceFiles = 639 arrayOf( 640 java( 641 """ 642 package androidx.collection; 643 644 import java.util.Collection; 645 import java.util.HashSet; 646 import java.util.Set; 647 648 public class ArraySet<E> extends HashSet<E> implements Set<E> { 649 public ArraySet() { 650 } 651 } 652 """ 653 ), 654 kotlin( 655 "src/main/java/androidx/collection/ArraySet.kt", 656 """ 657 package androidx.collection 658 659 inline fun <T> arraySetOf(): ArraySet<T> = ArraySet() 660 661 fun <T> arraySetOf(vararg values: T): ArraySet<T> { 662 val set = ArraySet<T>(values.size) 663 for (value in values) { 664 set.add(value) 665 } 666 return set 667 } 668 """ 669 ) 670 ) 671 ) 672 } 673 674 @Test Missing inner nullabilitynull675 fun `Missing inner nullability`() { 676 check( 677 apiLint = "", 678 extraArguments = arrayOf(ARG_ERROR, "MissingInnerNullability"), 679 expectedFail = DefaultLintErrorMessage, 680 expectedIssues = 681 """ 682 src/test/pkg/Foo.java:5: error: Missing nullability on method `getArray` return [MissingNullability] 683 src/test/pkg/Foo.java:5: error: Missing nullability on inner type java.lang.String in method `getArray` return [MissingInnerNullability] 684 src/test/pkg/Foo.java:6: error: Missing nullability on method `getMap` return [MissingNullability] 685 src/test/pkg/Foo.java:6: error: Missing nullability on inner type java.lang.Number in method `getMap` return [MissingInnerNullability] 686 src/test/pkg/Foo.java:6: error: Missing nullability on inner type java.lang.String in method `getMap` return [MissingInnerNullability] 687 src/test/pkg/Foo.java:7: error: Missing nullability on method `getWildcardMap` return [MissingNullability] 688 src/test/pkg/Foo.java:7: error: Missing nullability on inner type java.lang.Number in method `getWildcardMap` return [MissingInnerNullability] 689 src/test/pkg/Foo.java:7: error: Missing nullability on inner type java.lang.String in method `getWildcardMap` return [MissingInnerNullability] 690 """, 691 sourceFiles = 692 arrayOf( 693 java( 694 """ 695 package test.pkg; 696 import java.util.List; 697 import java.util.Map; 698 public class Foo { 699 public String[] getArray() { return null; } 700 public Map<Number, String> getMap() { return null; } 701 public Map<? extends Number, ? super String> getWildcardMap() { return null; } 702 } 703 """ 704 ) 705 ), 706 ) 707 } 708 709 @Test Test inner type nullness overrides of defined nullnessnull710 fun `Test inner type nullness overrides of defined nullness`() { 711 check( 712 apiLint = "", 713 extraArguments = arrayOf(ARG_HIDE, "NullableCollectionElement"), 714 expectedFail = DefaultLintErrorMessage, 715 expectedIssues = 716 """ 717 src/test/pkg/Foo.java:7: error: Invalid nullability on type java.lang.String method `foo` return. Method override cannot use a nullable type when the corresponding type from the super method is non-null. [InvalidNullabilityOverride] 718 src/test/pkg/Foo.java:7: error: Invalid nullability on type java.lang.String in parameter `arg` in method `foo`. Parameter in method override cannot use a non-null type when the corresponding type from the super method is nullable. [InvalidNullabilityOverride] 719 """, 720 sourceFiles = 721 arrayOf( 722 java( 723 """ 724 package test.pkg; 725 import java.util.List; 726 import libcore.util.NonNull; 727 import libcore.util.Nullable; 728 public class Superclass { 729 public @NonNull String @NonNull [] foo(@NonNull List<@Nullable String> arg) { return null; } 730 } 731 """ 732 ), 733 java( 734 """ 735 package test.pkg; 736 import java.util.List; 737 import libcore.util.NonNull; 738 import libcore.util.Nullable; 739 public class Foo extends Superclass { 740 @Override 741 public @Nullable String @NonNull [] foo(@NonNull List<@NonNull String> arg) { return null; } 742 } 743 """ 744 ), 745 libcoreNonNullSource, 746 libcoreNullableSource, 747 ) 748 ) 749 } 750 751 @Test Test inner type nullness overrides of platform nullnessnull752 fun `Test inner type nullness overrides of platform nullness`() { 753 // TODO (b/344859664): this case is ignored for now 754 check( 755 apiLint = "", 756 extraArguments = arrayOf(ARG_HIDE, "NullableCollectionElement"), 757 sourceFiles = 758 arrayOf( 759 java( 760 """ 761 package test.pkg; 762 import libcore.util.NonNull; 763 import libcore.util.Nullable; 764 public class Superclass { 765 public String @NonNull [] foo(@NonNull List<String> arg) { return null; } 766 } 767 """ 768 ), 769 java( 770 """ 771 package test.pkg; 772 import libcore.util.NonNull; 773 import libcore.util.Nullable; 774 public class Foo extends Superclass { 775 @Override 776 public @Nullable String @NonNull [] foo(@NonNull List<@NonNull String> arg) { return null; } 777 } 778 """ 779 ), 780 libcoreNonNullSource, 781 libcoreNullableSource, 782 ) 783 ) 784 } 785 } 786