From 86d0145b26d8a4c9e45c8bdfdf3091d8c4b0d508 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Thu, 23 Sep 2021 14:52:25 +0700 Subject: [PATCH] [java-dfa] Support JSR-305 unknown nullability Fixes IDEA-278618 JSR-305 @Nonnull(when = UNKNOWN) not handled correctly GitOrigin-RevId: 5983fbdcaad42dd5db8e1c8adb27cedbb9a746e2 --- .../NullableNotNullManagerImpl.java | 11 ++++++++ .../annoPackages/Jsr305Support.java | 23 +++++++++------- .../codeInsight/NullableNotNullManager.java | 8 +++++- .../dataFlow/fixture/NonNullWhenUnknown.java | 26 +++++++++++++++++++ .../DataFlowInspection8Test.java | 6 +++++ .../DataFlowInspectionTest.java | 2 +- 6 files changed, 65 insertions(+), 11 deletions(-) create mode 100644 java/java-tests/testData/inspection/dataFlow/fixture/NonNullWhenUnknown.java diff --git a/java/java-impl/src/com/intellij/codeInsight/NullableNotNullManagerImpl.java b/java/java-impl/src/com/intellij/codeInsight/NullableNotNullManagerImpl.java index aee2d0f8339b..04df44605e8f 100644 --- a/java/java-impl/src/com/intellij/codeInsight/NullableNotNullManagerImpl.java +++ b/java/java-impl/src/com/intellij/codeInsight/NullableNotNullManagerImpl.java @@ -399,6 +399,17 @@ public class NullableNotNullManagerImpl extends NullableNotNullManager implement }); } + @Override + protected @NotNull Nullability correctNullability(@NotNull Nullability nullability, @NotNull PsiAnnotation annotation) { + if (nullability == Nullability.NOT_NULL && annotation.hasQualifiedName(Jsr305Support.JAVAX_ANNOTATION_NONNULL)) { + Nullability correctedNullability = Jsr305Support.extractNullityFromWhenValue(annotation); + if (correctedNullability != null) { + return correctedNullability; + } + } + return nullability; + } + @Override public long getModificationCount() { return myTracker.getModificationCount(); diff --git a/java/java-impl/src/com/intellij/codeInsight/annoPackages/Jsr305Support.java b/java/java-impl/src/com/intellij/codeInsight/annoPackages/Jsr305Support.java index eb38ae9ae050..cb26f7f74446 100644 --- a/java/java-impl/src/com/intellij/codeInsight/annoPackages/Jsr305Support.java +++ b/java/java-impl/src/com/intellij/codeInsight/annoPackages/Jsr305Support.java @@ -59,19 +59,21 @@ public class Jsr305Support implements AnnotationPackageSupport { public static boolean isNullabilityNickName(@NotNull PsiClass candidate) { String qname = candidate.getQualifiedName(); if (qname == null || qname.startsWith("javax.annotation.")) return false; - return getNickNamedNullability(candidate) != Nullability.UNKNOWN; + return getNickNamedNullability(candidate) != null; } - @NotNull - public static Nullability getNickNamedNullability(@NotNull PsiClass psiClass) { - if (AnnotationUtil.findAnnotation(psiClass, TYPE_QUALIFIER_NICKNAME) == null) return Nullability.UNKNOWN; + /** + * @param psiClass annotation class + * @return nicknamed nullability declared by this annotation; null if this annotation is not a nullability nickname annotation + */ + public static @Nullable Nullability getNickNamedNullability(@NotNull PsiClass psiClass) { + if (AnnotationUtil.findAnnotation(psiClass, TYPE_QUALIFIER_NICKNAME) == null) return null; PsiAnnotation nonNull = AnnotationUtil.findAnnotation(psiClass, JAVAX_ANNOTATION_NONNULL); - return nonNull != null ? extractNullityFromWhenValue(nonNull) : Nullability.UNKNOWN; + return nonNull != null ? extractNullityFromWhenValue(nonNull) : null; } - @NotNull - private static Nullability extractNullityFromWhenValue(@NotNull PsiAnnotation nonNull) { + public static @Nullable Nullability extractNullityFromWhenValue(@NotNull PsiAnnotation nonNull) { PsiAnnotationMemberValue when = nonNull.findAttributeValue("when"); if (when instanceof PsiReferenceExpression) { String refName = ((PsiReferenceExpression)when).getReferenceName(); @@ -81,13 +83,16 @@ public class Jsr305Support implements AnnotationPackageSupport { if ("MAYBE".equals(refName) || "NEVER".equals(refName)) { return Nullability.NULLABLE; } + if ("UNKNOWN".equals(refName)) { + return Nullability.UNKNOWN; + } } // 'when' is unknown and annotation is known -> default value (for javax.annotation.Nonnull is ALWAYS) - if (when == null && JAVAX_ANNOTATION_NONNULL.equals(nonNull.getQualifiedName())) { + if (when == null) { return Nullability.NOT_NULL; } - return Nullability.UNKNOWN; + return null; } @NotNull diff --git a/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java b/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java index 25a16e692a21..69ad7fcad58d 100644 --- a/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java +++ b/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java @@ -237,7 +237,7 @@ public abstract class NullableNotNullManager { } }; NullabilityAnnotationInfo result = findPlainAnnotation(owner, false, false, filtered); - return result == null ? null : result.getAnnotation(); + return result == null || !nullabilities.contains(result.getNullability()) ? null : result.getAnnotation(); } private @Nullable NullabilityAnnotationInfo findPlainAnnotation( @@ -260,6 +260,7 @@ public abstract class NullableNotNullManager { if (memberAnno != null) { Nullability nullability = annotations.getNullability(memberAnno.annotation.getQualifiedName()); if (nullability == null) return null; + nullability = correctNullability(nullability, memberAnno.annotation); if (type != null) { for (PsiAnnotation typeAnno : type.getApplicableAnnotations()) { if (typeAnno == memberAnno.annotation) continue; @@ -279,6 +280,10 @@ public abstract class NullableNotNullManager { return findAnnotationInTypeHierarchy(type, annotations); } + protected @NotNull Nullability correctNullability(@NotNull Nullability nullability, @NotNull PsiAnnotation annotation) { + return nullability; + } + private static boolean areDifferentNullityAnnotations(@NotNull PsiAnnotation memberAnno, @NotNull PsiAnnotation typeAnno) { NullableNotNullManager manager = getInstance(memberAnno.getProject()); List notNulls = manager.getNotNullsWithNickNames(); @@ -437,6 +442,7 @@ public abstract class NullableNotNullManager { if (qualifiedNames.qualifiedNames().contains(qualifiedName)) { Nullability nullability = qualifiedNames.getNullability(qualifiedName); if (nullability != null) { + nullability = correctNullability(nullability, annotation); result.set(new NullabilityAnnotationInfo(annotation, nullability, false)); } return false; diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/NonNullWhenUnknown.java b/java/java-tests/testData/inspection/dataFlow/fixture/NonNullWhenUnknown.java new file mode 100644 index 000000000000..b617148f7bee --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/NonNullWhenUnknown.java @@ -0,0 +1,26 @@ +import javax.annotation.Nonnull; +import javax.annotation.ParametersAreNonnullByDefault; +import javax.annotation.meta.TypeQualifierNickname; +import javax.annotation.meta.When; + +@ParametersAreNonnullByDefault +final class Test { + @Nonnull(when = When.UNKNOWN) + @TypeQualifierNickname + @interface UnknownNullity {} + + @UnknownNullity + private final T value; + + @Nonnull(when = When.UNKNOWN) + private final T value2; + + public Test(@UnknownNullity T value, @Nonnull(when = When.UNKNOWN) T value2) { + this.value = value; + this.value2 = value2; + } + + public boolean isNull() { + return value == null || value2 == null; + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java index f28d90edc057..e5b2f46676eb 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java @@ -121,6 +121,12 @@ public class DataFlowInspection8Test extends DataFlowInspectionTestCase { doTest(); } + public void testNonNullWhenUnknown() { + DataFlowInspectionTest.addJavaxNullabilityAnnotations(myFixture); + DataFlowInspectionTest.addJavaxDefaultNullabilityAnnotations(myFixture); + doTest(); + } + public void testReturningNullFromTypeAnnotatedNullableMethod() { doTestWithCustomAnnotations(); } diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java index dbcf63c8f179..65158eb939d5 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java @@ -354,7 +354,6 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase { } public void testTypeQualifierNickname() { - myFixture.addClass("package javax.annotation.meta; public @interface TypeQualifierNickname {}"); addJavaxNullabilityAnnotations(myFixture); myFixture.addClass(barNullableNick()); @@ -386,6 +385,7 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase { } public static void addJavaxNullabilityAnnotations(final JavaCodeInsightTestFixture fixture) { + fixture.addClass("package javax.annotation.meta; public @interface TypeQualifierNickname {}"); fixture.addClass("package javax.annotation.meta;" + "public @interface TypeQualifierDefault { java.lang.annotation.ElementType[] value() default {};}"); fixture.addClass("package javax.annotation.meta;" +