From 38de0d9ee7e4ab960f6ef22e92a38f5ff3bc312e Mon Sep 17 00:00:00 2001 From: Marcin Mikosik Date: Fri, 13 Feb 2026 10:13:39 +0000 Subject: [PATCH] IDEA-385435 [java]: Add inspection detecting effectively non-null param overriding not-annotated one Merge-request: IJ-MR-190382 Merged-by: Marcin Mikosik GitOrigin-RevId: 0a755a3eb1749b7971fe42dea0bceaa2df3a6bb7 --- .../nullable/NullableStuffInspectionBase.java | 6 +- ...DefaultParameterOverridesNotAnnotated.java | 4 +- ...llByDefaultParameterOverridesNullable.java | 15 +++++ ...lMarkedParameterOverridesNotAnnotated.java | 66 +++++++++++++++++++ .../NullableStuffInspectionTest.java | 14 ++++ 5 files changed, 100 insertions(+), 5 deletions(-) create mode 100644 java/java-tests/testData/inspection/nullableProblems/NotNullByDefaultParameterOverridesNullable.java create mode 100644 java/java-tests/testData/inspection/nullableProblems/NullMarkedParameterOverridesNotAnnotated.java diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspectionBase.java b/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspectionBase.java index 0617af3924f7..20da5cc2eeb5 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspectionBase.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspectionBase.java @@ -1145,7 +1145,7 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection "inspection.nullable.problems.parameter.overrides.NotNull", getPresentableAnnoName(notNullSuper)); } if (isNotNullParameterOverridingNonAnnotated(nullableManager, parameter, superParameters)) { - NullabilityAnnotationInfo info = nullableManager.findOwnNullabilityInfo(parameter); + NullabilityAnnotationInfo info = nullableManager.findEffectiveNullabilityInfo(parameter); assert info != null; PsiAnnotation notNullAnnotation = info.getAnnotation(); boolean physical = PsiTreeUtil.isAncestor(parameter, notNullAnnotation, true); @@ -1193,8 +1193,8 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection PsiParameter parameter, List superParameters) { if (!REPORT_NOTNULL_PARAMETERS_OVERRIDES_NOT_ANNOTATED) return false; - NullabilityAnnotationInfo info = nullableManager.findOwnNullabilityInfo(parameter); - return info != null && info.getNullability() == Nullability.NOT_NULL && !info.isInferred() && + NullabilityAnnotationInfo info = nullableManager.findEffectiveNullabilityInfo(parameter); + return info != null && info.getNullability() == Nullability.NOT_NULL && info.getInheritedFrom() == null && !info.isInferred() && ContainerUtil.exists(superParameters, sp -> isSuperNotAnnotated(nullableManager, parameter, sp)); } diff --git a/java/java-tests/testData/inspection/nullableProblems/NotNullByDefaultParameterOverridesNotAnnotated.java b/java/java-tests/testData/inspection/nullableProblems/NotNullByDefaultParameterOverridesNotAnnotated.java index a74df54797db..3f64ae48bc78 100644 --- a/java/java-tests/testData/inspection/nullableProblems/NotNullByDefaultParameterOverridesNotAnnotated.java +++ b/java/java-tests/testData/inspection/nullableProblems/NotNullByDefaultParameterOverridesNotAnnotated.java @@ -7,8 +7,8 @@ class Test { @ParametersAreNonnullByDefault static class X extends XX { - // Do not warn as ParametersAreNonnullByDefault does not work for overridden parameters - void get(Object x) { + // Warn: parameter is effectively non-null via @ParametersAreNonnullByDefault + void get(Object x) { } } diff --git a/java/java-tests/testData/inspection/nullableProblems/NotNullByDefaultParameterOverridesNullable.java b/java/java-tests/testData/inspection/nullableProblems/NotNullByDefaultParameterOverridesNullable.java new file mode 100644 index 000000000000..83b937cb1776 --- /dev/null +++ b/java/java-tests/testData/inspection/nullableProblems/NotNullByDefaultParameterOverridesNullable.java @@ -0,0 +1,15 @@ +import javax.annotation.ParametersAreNonnullByDefault; +import javax.annotation.Nullable; + +class Test { + static class XX { + void get(@Nullable Object t) {} + } + + @ParametersAreNonnullByDefault + static class X extends XX { + void get(Object x) { + + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/nullableProblems/NullMarkedParameterOverridesNotAnnotated.java b/java/java-tests/testData/inspection/nullableProblems/NullMarkedParameterOverridesNotAnnotated.java new file mode 100644 index 000000000000..a58dcbe81625 --- /dev/null +++ b/java/java-tests/testData/inspection/nullableProblems/NullMarkedParameterOverridesNotAnnotated.java @@ -0,0 +1,66 @@ +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +// Direct inheritance tests + +class UnmarkedSuper { + void method(String param) {} + void methodWithTwoParams(String first, String second) {} +} + +@NullMarked +class NullMarkedSubclass extends UnmarkedSuper { + @Override + void method(String param) {} + + @Override + void methodWithTwoParams(String first, + String second) {} +} + +@NullMarked +class NullMarkedSubclassWithNullable extends UnmarkedSuper { + @Override + void method(@Nullable String param) {} +} + +@NullMarked +class NullMarkedSuper { + void method(String param) {} +} + +@NullMarked +class NullMarkedSubclass2 extends NullMarkedSuper { + @Override + void method(String param) {} +} + +// Indirect inheritance test + +interface UnmarkedInterface { + void interfaceMethod(String param); +} + +@NullMarked +class NullMarkedBase { + public void interfaceMethod(String param) {} // effectively non-null via @NullMarked +} + +abstract class IndirectSubclass extends NullMarkedBase implements UnmarkedInterface {} + +// Multi-level inheritance test + +class UnmarkedBase2 { + void chainMethod(String param) {} +} + +class UnmarkedIntermediate extends UnmarkedBase2 { + @Override + void chainMethod(String param) {} +} + +@NullMarked +class NullMarkedChainEnd extends UnmarkedIntermediate { + @Override + void chainMethod(String param) {} +} diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/NullableStuffInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/NullableStuffInspectionTest.java index 6028e04b87d0..960954918212 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/NullableStuffInspectionTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/NullableStuffInspectionTest.java @@ -144,6 +144,13 @@ public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTest doTest(); } + public void testNullMarkedParameterOverridesNotAnnotated() { + myInspection.REPORT_NOTNULL_PARAMETERS_OVERRIDES_NOT_ANNOTATED = true; + addJSpecifyNullMarked(myFixture); + setupTypeUseAnnotations("org.jspecify.annotations", myFixture); + doTest(); + } + public void testNotNullByDefaultParameterOverridesNotAnnotated() { myInspection.REPORT_NOTNULL_PARAMETERS_OVERRIDES_NOT_ANNOTATED = true; addJavaxNullabilityAnnotations(myFixture); @@ -151,6 +158,13 @@ public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTest doTest(); } + public void testNotNullByDefaultParameterOverridesNullable() { + myInspection.REPORT_NOTNULL_PARAMETERS_OVERRIDES_NOT_ANNOTATED = true; + addJavaxNullabilityAnnotations(myFixture); + DataFlowInspectionTest.addJavaxDefaultNullabilityAnnotations(myFixture); + doTest(); + } + public void testNullableCalledWithNullUnderNotNullByDefault() { addJavaxNullabilityAnnotations(myFixture); DataFlowInspectionTest.addJavaxDefaultNullabilityAnnotations(myFixture);