diff --git a/its/autoscan/src/test/resources/autoscan/autoscan-diff-by-rules.json b/its/autoscan/src/test/resources/autoscan/autoscan-diff-by-rules.json index 5108ce8880d..8692163693d 100644 --- a/its/autoscan/src/test/resources/autoscan/autoscan-diff-by-rules.json +++ b/its/autoscan/src/test/resources/autoscan/autoscan-diff-by-rules.json @@ -218,7 +218,7 @@ { "ruleKey": "S1128", "hasTruePositives": true, - "falseNegatives": 32, + "falseNegatives": 34, "falsePositives": 0 }, { @@ -320,7 +320,7 @@ { "ruleKey": "S1168", "hasTruePositives": true, - "falseNegatives": 6, + "falseNegatives": 24, "falsePositives": 0 }, { @@ -338,7 +338,7 @@ { "ruleKey": "S1172", "hasTruePositives": true, - "falseNegatives": 13, + "falseNegatives": 21, "falsePositives": 0 }, { @@ -1286,7 +1286,7 @@ { "ruleKey": "S2447", "hasTruePositives": true, - "falseNegatives": 0, + "falseNegatives": 4, "falsePositives": 0 }, { @@ -1298,13 +1298,13 @@ { "ruleKey": "S2583", "hasTruePositives": true, - "falseNegatives": 21, + "falseNegatives": 20, "falsePositives": 0 }, { "ruleKey": "S2589", "hasTruePositives": true, - "falseNegatives": 5, + "falseNegatives": 6, "falsePositives": 0 }, { @@ -1322,13 +1322,13 @@ { "ruleKey": "S2637", "hasTruePositives": true, - "falseNegatives": 21, + "falseNegatives": 87, "falsePositives": 0 }, { "ruleKey": "S2638", "hasTruePositives": true, - "falseNegatives": 7, + "falseNegatives": 9, "falsePositives": 0 }, { @@ -1442,7 +1442,7 @@ { "ruleKey": "S2789", "hasTruePositives": true, - "falseNegatives": 11, + "falseNegatives": 37, "falsePositives": 0 }, { @@ -1664,7 +1664,7 @@ { "ruleKey": "S3516", "hasTruePositives": true, - "falseNegatives": 11, + "falseNegatives": 8, "falsePositives": 0 }, { @@ -1766,7 +1766,7 @@ { "ruleKey": "S3958", "hasTruePositives": true, - "falseNegatives": 2, + "falseNegatives": 4, "falsePositives": 0 }, { @@ -2018,7 +2018,7 @@ { "ruleKey": "S4682", "hasTruePositives": true, - "falseNegatives": 0, + "falseNegatives": 8, "falsePositives": 0 }, { @@ -2036,7 +2036,7 @@ { "ruleKey": "S4738", "hasTruePositives": false, - "falseNegatives": 55, + "falseNegatives": 68, "falsePositives": 0 }, { diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S1128.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S1128.json index e7b8f371752..b1c359a59f5 100644 --- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S1128.json +++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S1128.json @@ -1,6 +1,6 @@ { "ruleKey": "S1128", "hasTruePositives": true, - "falseNegatives": 33, + "falseNegatives": 34, "falsePositives": 0 -} \ No newline at end of file +} diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S1168.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S1168.json index 6e432b47b38..8887c268f44 100644 --- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S1168.json +++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S1168.json @@ -1,6 +1,6 @@ { "ruleKey": "S1168", "hasTruePositives": true, - "falseNegatives": 6, + "falseNegatives": 24, "falsePositives": 0 -} \ No newline at end of file +} diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S1172.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S1172.json index 04d178b0efb..13f271882e6 100644 --- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S1172.json +++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S1172.json @@ -1,6 +1,6 @@ { "ruleKey": "S1172", "hasTruePositives": true, - "falseNegatives": 17, + "falseNegatives": 21, "falsePositives": 0 } diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S2447.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S2447.json index ad4b63238dc..a5f90015d22 100644 --- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S2447.json +++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S2447.json @@ -1,6 +1,6 @@ { "ruleKey": "S2447", "hasTruePositives": true, - "falseNegatives": 0, + "falseNegatives": 4, "falsePositives": 0 -} \ No newline at end of file +} diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S2583.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S2583.json index 008c636a2db..0aebde4ee92 100644 --- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S2583.json +++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S2583.json @@ -1,6 +1,6 @@ { "ruleKey": "S2583", "hasTruePositives": true, - "falseNegatives": 12, + "falseNegatives": 20, "falsePositives": 0 -} \ No newline at end of file +} diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S2589.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S2589.json index c6677e44ec0..b3944cdee6a 100644 --- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S2589.json +++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S2589.json @@ -1,6 +1,6 @@ { "ruleKey": "S2589", "hasTruePositives": true, - "falseNegatives": 4, + "falseNegatives": 6, "falsePositives": 0 -} \ No newline at end of file +} diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S2637.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S2637.json index 59d10c6656e..4951d6e3e93 100644 --- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S2637.json +++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S2637.json @@ -1,6 +1,6 @@ { "ruleKey": "S2637", "hasTruePositives": true, - "falseNegatives": 21, + "falseNegatives": 87, "falsePositives": 0 -} \ No newline at end of file +} diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S2638.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S2638.json index 3b45fb50c85..6ed2e968b08 100644 --- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S2638.json +++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S2638.json @@ -1,6 +1,6 @@ { "ruleKey": "S2638", "hasTruePositives": true, - "falseNegatives": 12, + "falseNegatives": 14, "falsePositives": 0 } diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S2789.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S2789.json index d770d1dfa54..94076c3803f 100644 --- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S2789.json +++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S2789.json @@ -1,6 +1,6 @@ { "ruleKey": "S2789", "hasTruePositives": true, - "falseNegatives": 17, + "falseNegatives": 43, "falsePositives": 0 } diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S3516.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S3516.json index 5442dbd84ff..8c5c9d3a844 100644 --- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S3516.json +++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S3516.json @@ -1,6 +1,6 @@ { "ruleKey": "S3516", "hasTruePositives": true, - "falseNegatives": 6, + "falseNegatives": 8, "falsePositives": 0 -} \ No newline at end of file +} diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S3958.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S3958.json index d5f79b49df5..5ed2023b9df 100644 --- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S3958.json +++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S3958.json @@ -1,6 +1,6 @@ { "ruleKey": "S3958", "hasTruePositives": true, - "falseNegatives": 2, + "falseNegatives": 4, "falsePositives": 0 -} \ No newline at end of file +} diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S4682.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S4682.json index 262b982dbdb..1a9c6a09fe0 100644 --- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S4682.json +++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S4682.json @@ -1,6 +1,6 @@ { "ruleKey": "S4682", "hasTruePositives": true, - "falseNegatives": 2, + "falseNegatives": 10, "falsePositives": 0 } diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S4738.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S4738.json index b898cd6e94b..4443748cb59 100644 --- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S4738.json +++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S4738.json @@ -1,6 +1,6 @@ { "ruleKey": "S4738", "hasTruePositives": false, - "falseNegatives": 46, + "falseNegatives": 70, "falsePositives": 0 } diff --git a/java-checks-test-sources/default/pom.xml b/java-checks-test-sources/default/pom.xml index 1c9e0083f50..163513289b7 100644 --- a/java-checks-test-sources/default/pom.xml +++ b/java-checks-test-sources/default/pom.xml @@ -42,6 +42,12 @@ 3) Several plugins are disabled bellow to not generate jars --> + + + org.jspecify + jspecify + 1.0.0 + org.checkerframework checker-compat-qual diff --git a/java-checks-test-sources/default/src/main/java/checks/jspecify/BooleanMethodReturnCheckNullMarked.java b/java-checks-test-sources/default/src/main/java/checks/jspecify/BooleanMethodReturnCheckNullMarked.java new file mode 100644 index 00000000000..57dc195a9e3 --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/jspecify/BooleanMethodReturnCheckNullMarked.java @@ -0,0 +1,83 @@ +package checks.jspecify; + +import java.util.stream.Stream; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.NullUnmarked; + +@NullMarked +class BooleanMethodReturnCheckJSpecifySampleA { + public Boolean myMethod() { + return null; // Noncompliant {{Null is returned but a "Boolean" is expected.}} + } + + public Boolean myOtherMethod() { + return Boolean.TRUE; // Compliant + } + + BooleanMethodReturnCheckJSpecifySampleA() { + // constructor (with null return type) are not covered by the rule + return; + } + + @Nullable + public Boolean foo() { + return null; // Compliant + } + + @NullUnmarked + public Boolean bar() { + return null; // Compliant + } +} + +@NullMarked +class BooleanMethodReturnCheckJSpecifySampleB { + private class Boolean { + } + + public Boolean myMethodFailing() { + return null; // Compliant + } + + public java.lang.Boolean myOtherMethod() { + class BooleanMethodReturnCheckSampleC { + private java.lang.Boolean myInnerMethod() { + return null; // Noncompliant {{Null is returned but a "Boolean" is expected.}} + } + private BooleanMethodReturnCheckSampleC foo() { + return null; // Compliant + } + } + return null; // Noncompliant {{Null is returned but a "Boolean" is expected.}} +// ^^^^ + } + + @CheckForNull + public java.lang.Boolean myMethod2() { + return null; // compliant method is annotated with @CheckForNull + } +} + +@NullMarked +class BooleanMethodReturnCheckJSpecifySampleD { + public Boolean foo() { + class BooleanMethodReturnCheckSampleE { + void bar() { + return; + } + } + Stream.of("A").forEach(a -> { + return; // Compliant + }); + return true; + } +} + +class BooleanMethodReturnCheckJSpecifySampleE { + @NullMarked + public Boolean myMethod() { + return null; // Noncompliant {{Null is returned but a "Boolean" is expected.}} + } +} diff --git a/java-checks-test-sources/default/src/main/java/checks/jspecify/ChangeMethodContractCheckNullMarked.java b/java-checks-test-sources/default/src/main/java/checks/jspecify/ChangeMethodContractCheckNullMarked.java new file mode 100644 index 00000000000..730323970ba --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/jspecify/ChangeMethodContractCheckNullMarked.java @@ -0,0 +1,22 @@ +package checks.jspecify; + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.NullUnmarked; + +@NullMarked +class ChangeMethodContractCheck { + + @interface MyAnnotation {} + + @NullUnmarked + String annotatedUnmarked(Object a) { return null; } +} + +class ChangeMethodContractCheck_B extends ChangeMethodContractCheck { + + @NullMarked + @Override + String annotatedUnmarked(Object a) { return null; } // Noncompliant {{Fix the incompatibility of the annotation @NullMarked to honor @NullUnmarked of the overridden method.}} + +} + diff --git a/java-checks-test-sources/default/src/main/java/checks/jspecify/EqualsParametersMarkedNonNullCheckNullMarked.java b/java-checks-test-sources/default/src/main/java/checks/jspecify/EqualsParametersMarkedNonNullCheckNullMarked.java new file mode 100644 index 00000000000..813df3387ed --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/jspecify/EqualsParametersMarkedNonNullCheckNullMarked.java @@ -0,0 +1,23 @@ +package checks.jspecify; + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.NullUnmarked; + +@NullMarked +class EqualsParametersMarkedNonNullCheckSampleA { + + // @NullUnmarked not applicable to parameter + public boolean equals(Object obj) { // Compliant + return true; + } +} + +@NullMarked +class EqualsParametersMarkedNonNullCheckSampleB { + + @NullUnmarked + // @NullUnmarked not applicable to parameter + public boolean equals(Object obj) { // Compliant + return true; + } +} diff --git a/java-checks-test-sources/default/src/main/java/checks/jspecify/NullShouldNotBeUsedWithOptionalCheckNullMarked_guava.java b/java-checks-test-sources/default/src/main/java/checks/jspecify/NullShouldNotBeUsedWithOptionalCheckNullMarked_guava.java new file mode 100644 index 00000000000..5ba2a8e138c --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/jspecify/NullShouldNotBeUsedWithOptionalCheckNullMarked_guava.java @@ -0,0 +1,115 @@ +package checks.jspecify; +// To help keep "guava" and "jdk" tests in sync, this file is identical to its counterpart except for the import of class "Optional" + +import com.google.common.base.Optional; +import java.util.List; +import javax.annotation.Nullable; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.NullUnmarked; + +@NullMarked +interface NullShouldNotBeUsedWithOptionalCheck_guava { + + @NullUnmarked // Noncompliant {{Methods with an "Optional" return type should not be "@NullUnmarked".}} +//^^^^^^^^^^^^^ + public Optional getOptionalKo(); + +} + +@NullMarked +class NullShouldNotBeUsedWithOptionalCheck_guavaClassA { + + public NullShouldNotBeUsedWithOptionalCheck_guavaClassA() { + } + + @NullUnmarked // Noncompliant {{Methods with an "Optional" return type should not be "@NullUnmarked".}} +//^^^^^^^^^^^^^ + public Optional getOptionalKo() { + return null; // Noncompliant {{Methods with an "Optional" return type should never return null.}} +// ^^^^ + } + + public Optional getOptionalOk() { + return Optional.of("hello"); + } + + public Object doSomething1() { + return null; + } + + public Optional doSomething2() { + Worker x = new Worker() { + public String work() { + return null; + } + }; + return Optional.of("hello"); + } + + public int doSomething3(Optional arg) { + if (arg == null) { // Noncompliant {{Ensure this "Optional" could never be null and remove this null-check.}} +// ^^^^^^^^^^^ + return 0; + } + + Optional optional = getOptionalOk(); + if (optional == null) { // Noncompliant {{Ensure this "Optional" could never be null and remove this null-check.}} +// ^^^^^^^^^^^^^^^^ + return 0; + } else if (null != optional) { // Noncompliant {{Ensure this "Optional" could never be null and remove this null-check.}} +// ^^^^^^^^^^^^^^^^ + return 0; + } + + Optional optional2 = getOptionalOk(); + if (optional == optional2) { + return 0; + } else if (null == null) { + return 0; + } + + Optional optional3 = getOptionalOk(); + return optional3 == null ? 0 : 1; // Noncompliant {{Ensure this "Optional" could never be null and remove this null-check.}} +// ^^^^^^^^^^^^^^^^^ + } + + public Optional doSomething4(List myList) { + myList.stream().map(s -> { + if (s.length() > 0) { + return null; + } + return s; + }); + return Optional.of("hello"); + } + + @Deprecated + public Optional doSomething5(List myList) { + return myList.isEmpty() ? Optional.of("hello") : null; // Noncompliant {{Methods with an "Optional" return type should never return null.}} +// ^^^^ + } + + @Nullable // Noncompliant {{"Optional" variables should not be "@Nullable".}} +//^^^^^^^^^ + private Optional field; + + public void doSomething6(@Nullable Optional arg) { // Noncompliant {{"Optional" variables should not be "@Nullable".}} +// ^^^^^^^^^ + } + + public void doSomething7() { + @Nullable // Noncompliant {{"Optional" variables should not be "@Nullable".}} +// ^^^^^^^^^ + Optional var; + } + + public Optional doSomething8(boolean b) { + Object obj = b ? null : new Object(); + return Optional.of("hello"); + } + + interface Worker { + String work(); + } + +} diff --git a/java-checks-test-sources/default/src/main/java/checks/jspecify/NullShouldNotBeUsedWithOptionalCheckNullMarked_jdk.java b/java-checks-test-sources/default/src/main/java/checks/jspecify/NullShouldNotBeUsedWithOptionalCheckNullMarked_jdk.java new file mode 100644 index 00000000000..4f3ba39af7c --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/jspecify/NullShouldNotBeUsedWithOptionalCheckNullMarked_jdk.java @@ -0,0 +1,151 @@ +package checks.jspecify; +// To help keep "guava" and "jdk" tests in sync, this file is identical to its counterpart except for the import of class "Optional" + +import java.util.List; +import java.util.Optional; +import javax.annotation.Nullable; +import javax.annotation.meta.When; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.NullUnmarked; + +@NullMarked +interface NullShouldNotBeUsedWithOptionalCheck_jdk { + + @NullUnmarked // Noncompliant {{Methods with an "Optional" return type should not be "@NullUnmarked".}} +//^^^^^^^^^^^^^ + public Optional getOptionalKo(); + +} + +@NullMarked +class NullShouldNotBeUsedWithOptionalCheck_jdkClassA { + + public NullShouldNotBeUsedWithOptionalCheck_jdkClassA() { + } + + @NullUnmarked // Noncompliant {{Methods with an "Optional" return type should not be "@NullUnmarked".}} +//^^^^^^^^^^^^^ + public Optional getOptionalKo() { + return null; // Noncompliant {{Methods with an "Optional" return type should never return null.}} +// ^^^^ + } + + public Optional getOptionalOk() { + return Optional.of("hello"); + } + + public Object doSomething1() { + return null; + } + + public Optional doSomething2() { + Worker x = new Worker() { + public String work() { + return null; + } + }; + return Optional.of("hello"); + } + + public int doSomething3(Optional arg) { + if (arg == null) { // Noncompliant {{Ensure this "Optional" could never be null and remove this null-check.}} +// ^^^^^^^^^^^ + return 0; + } + + Optional optional = getOptionalOk(); + if (optional == null) { // Noncompliant {{Ensure this "Optional" could never be null and remove this null-check.}} +// ^^^^^^^^^^^^^^^^ + return 0; + } else if (null != optional) { // Noncompliant {{Ensure this "Optional" could never be null and remove this null-check.}} +// ^^^^^^^^^^^^^^^^ + return 0; + } + + Optional optional2 = null; // Noncompliant {{Replace this null literal by an "Optional" object.}} +// ^^^^ + String notOptional = null; // Compliant + optional = null; // Noncompliant {{Replace this null literal by an "Optional" object.}} +// ^^^^ + optional = Optional.empty(); // Compliant + notOptional = null; // Compliant + if (optional == optional2) { + return 0; + } else if (null == null) { + return 0; + } + + Optional optional3 = getOptionalOk(); + return optional3 == null ? 0 : 1; // Noncompliant {{Ensure this "Optional" could never be null and remove this null-check.}} +// ^^^^^^^^^^^^^^^^^ + } + + public Optional doSomething4(List myList) { + myList.stream().map(s -> { + if (s.length() > 0) { + return null; + } + return s; + }); + return Optional.of("hello"); + } + + @Deprecated + public Optional doSomething5(List myList) { + return myList.isEmpty() ? Optional.of("hello") : null; // Noncompliant {{Methods with an "Optional" return type should never return null.}} +// ^^^^ + } + + @Nullable // Noncompliant {{"Optional" variables should not be "@Nullable".}} +//^^^^^^^^^ + private Optional field = null; // Noncompliant {{Replace this null literal by an "Optional" object.}} +// ^^^^ + + public void doSomething6(@Nullable Optional arg) { // Noncompliant {{"Optional" variables should not be "@Nullable".}} +// ^^^^^^^^^ + } + + public void doSomething7() { + @Nullable // Noncompliant {{"Optional" variables should not be "@Nullable".}} +// ^^^^^^^^^ + Optional var; + } + + public void NonnullWithArgument1() { + @javax.annotation.Nonnull(when= When.MAYBE) // Noncompliant {{"Optional" variables should not be "@Nonnull(when=MAYBE)".}} +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Optional var; + } + + public void NonnullWithArgument2() { + @javax.annotation.Nonnull(when= When.NEVER) // Noncompliant {{"Optional" variables should not be "@Nonnull(when=NEVER)".}} +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Optional var; + } + + public void NonnullWithArgument3() { + @javax.annotation.Nonnull(when= When.UNKNOWN) // Noncompliant {{"Optional" variables should not be "@Nonnull(when=UNKNOWN)".}} +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Optional var; + } + + public void NonnullWithArgument4() { + @javax.annotation.Nonnull(when= When.ALWAYS) // Compliant: when=ALWAYS is Nonnull + Optional var; + } + + public void NonnullWithArgument5() { + @javax.annotation.Nonnull() // Compliant + Optional var; + } + + public Optional doSomething8(boolean b) { + Object obj = b ? null : new Object(); + return Optional.of("hello"); + } + + interface Worker { + String work(); + } + +} diff --git a/java-checks-test-sources/default/src/main/java/checks/jspecify/PrimitivesMarkedNullableCheckNullMarked.java b/java-checks-test-sources/default/src/main/java/checks/jspecify/PrimitivesMarkedNullableCheckNullMarked.java new file mode 100644 index 00000000000..6f9db4265c3 --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/jspecify/PrimitivesMarkedNullableCheckNullMarked.java @@ -0,0 +1,72 @@ +package checks.jspecify; + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.NullUnmarked; + +@interface AnotherAnnotation { +} + +@NullMarked +@interface MyCheckForNull { +} + +@NullMarked +abstract class PrimitivesMarkedNullableCheckSample { + + abstract int getInt1(); + + @AnotherAnnotation + abstract int getInt2(); + + @NullUnmarked + protected abstract boolean isBool(); // Noncompliant {{"@NullUnmarked" annotation should not be used on primitive types}} [[quickfixes=qf1]] +// ^^^^^^^ + // fix@qf1 {{Remove "@NullUnmarked"}} + // edit@qf1 [[sl=-1;el=+0;sc=3;ec=3]] {{}} + + @NullUnmarked + public double getDouble1() { return 0.0; } // Noncompliant {{"@NullUnmarked" annotation should not be used on primitive types}} + + public double getDouble2() { return 0.0; } + + @MyCheckForNull + public double getDouble2_1() { return 0.0; } // Compliant, Nullable meta annotation are not taken into account + + @NullMarked + public double getDouble2_2() { return 0.0; } // Compliant, Nonnull is useless, but is accepted as it can be added for consistency + + @NullUnmarked + public Double getDouble3() { return 0.0; } + + @NullUnmarked + public Double getDouble4() { return 0.0; } + + @NullUnmarked + public Object getObj0() { return null; } + + @NullUnmarked + public Object getObj1() { return null; } + + public Object getObj2() { return null; } + + protected abstract @NullUnmarked boolean isBool2(); // Noncompliant {{"@NullUnmarked" annotation should not be used on primitive types}} [[quickfixes=qf3]] +// ^^^^^^^ + // fix@qf3 {{Remove "@NullUnmarked"}} + // edit@qf3 [[sc=22;ec=36]] {{}} + + @NullUnmarked + Object containsAnonymousClass() { + return new PrimitivesMarkedNullableCheckNullMarkedParent() { + int getInt0() { + return 0; + } + }; + } + +} + +abstract class PrimitivesMarkedNullableCheckNullMarkedChild extends PrimitivesMarkedNullableCheckNullMarkedParent { + + abstract int getInt0(); // Compliant, not directly marked as CheckForNull, an issue will be on the parent if needed. + +} diff --git a/java-checks-test-sources/default/src/main/java/checks/jspecify/PrimitivesMarkedNullableCheckNullMarkedParent.java b/java-checks-test-sources/default/src/main/java/checks/jspecify/PrimitivesMarkedNullableCheckNullMarkedParent.java new file mode 100644 index 00000000000..112a1522c14 --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/jspecify/PrimitivesMarkedNullableCheckNullMarkedParent.java @@ -0,0 +1,10 @@ +package checks.jspecify; + +import org.jspecify.annotations.NullMarked; + +abstract class PrimitivesMarkedNullableCheckNullMarkedParent { + + @NullMarked + abstract int getInt0(); // Noncompliant + +} diff --git a/java-checks-test-sources/default/src/main/java/checks/jspecify/ReturnEmptyArrayNotNullCheckNullMarked.java b/java-checks-test-sources/default/src/main/java/checks/jspecify/ReturnEmptyArrayNotNullCheckNullMarked.java new file mode 100644 index 00000000000..c4fb8d2b1c4 --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/jspecify/ReturnEmptyArrayNotNullCheckNullMarked.java @@ -0,0 +1,194 @@ +package checks.jspecify; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.NullUnmarked; +import org.springframework.batch.item.ItemProcessor; + +@NullMarked +class ReturnEmptyArrayNotNullCheckSampleA { + + public ReturnEmptyArrayNotNullCheckSampleA() { + return; + } + + public void f1() { + return; + } + + public int[] f2() { + return null; // Noncompliant {{Return an empty array instead of null.}} +// ^^^^ + } + + public Object f3() { + return null; // Compliant as not an array + } + + public Object f4()[] { + return null; // Noncompliant + } + + public int[] f5(boolean cond) { + new ReturnEmptyArrayNotNullCheckSampleB() { + public Object g1() { + return null; + } + + public int[] g2() { + return null; // Noncompliant + } + }; + if (cond) { + return new int[0]; + } + return null; // Noncompliant + } + + public List f6() { + return null; // Noncompliant + } + + public ArrayList f7() { + return null; // Noncompliant + } + + public Set f8(boolean cond) { + if (cond) { + return Collections.EMPTY_SET; + } + return null; // Noncompliant {{Return an empty collection instead of null.}} + } + + public List[] f9() { + return null; // Noncompliant {{Return an empty array instead of null.}} + } + + public java.util.Collection f10() { + return null; // Noncompliant + } +} + +@NullMarked +interface ReturnEmptyArrayNotNullCheckSampleB{ + default int[] a(){ + return null; // Noncompliant + } + + default int[] b(){ + return new int[4]; + } + + default List c(){ + return null; // Noncompliant + } + + default List d(){ + return new ArrayList(); + } + + default int[] e(){ + return null; // Noncompliant + } + + default int[] f(){ + return new int[4]; + } +} + +@NullMarked +class ReturnEmptyArrayNotNullCheckSampleC { + @SuppressWarnings("Something") + public int[] gul() { + return null; // Noncompliant + } + + @NullUnmarked + public Object foo() { + return null; + } + + @CheckForNull + public Object bar() { + return null; + } + + @NullUnmarked + public int[] fool() { + return null; + } + + @CheckForNull + public int[] bark() { + return null; + } + + int[] qix(){ + takeLambda(a -> { + return null; + }); + return new int[1]; + } + + static final Object CONSTANT = takeLambda(a->{return null;}); + + private static Object takeLambda(Function o) { + return o.apply(""); + } +} + +@NullMarked +class ReturnEmptyArrayNotNullCheckSampleD implements ItemProcessor> { + @Override + public List process(Integer i) { + return null; // Compliant: ItemProcessor requires to return null value to stop the processing + } + + public List process2(Integer i) { + return null; // Noncompliant + } +} + +@NullMarked +interface ReturnEmptyArrayNotNullCheckSampleE { + List bar(); +} + +@NullMarked +class ReturnEmptyArrayNotNullCheckSampleF implements ReturnEmptyArrayNotNullCheckSampleE { + @Override + public List bar() { + return null; // Noncompliant + } +} + +@NullMarked +class ReturnEmptyArrayNotNullCheckSampleG implements ItemProcessor>, ReturnEmptyArrayNotNullCheckSampleE { + @Override + public List bar() { + return null; // Noncompliant + } + + @Override + public List process(Integer integer) throws Exception { + return Collections.emptyList(); + } +} + +@NullMarked +class ReturnEmptyArrayNotNullCheckSampleH implements ItemProcessor { + @Override + public Integer[] process(Integer a) { + return null; // Compliant + } + + public int[] process(String a) { + return null; // Noncompliant + } +} diff --git a/java-checks-test-sources/default/src/main/java/checks/jspecify/nullmarked/BooleanMethodReturnCheck.java b/java-checks-test-sources/default/src/main/java/checks/jspecify/nullmarked/BooleanMethodReturnCheck.java new file mode 100644 index 00000000000..d8510a95955 --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/jspecify/nullmarked/BooleanMethodReturnCheck.java @@ -0,0 +1,80 @@ +package checks.jspecify.nullmarked; + +import java.util.stream.Stream; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import org.jspecify.annotations.NullUnmarked; + +// NullMarked at the package level +class BooleanMethodReturnCheckJSpecifySampleA { + public Boolean myMethod() { + return null; // Noncompliant {{Null is returned but a "Boolean" is expected.}} + } + + public Boolean myOtherMethod() { + return Boolean.TRUE; // Compliant + } + + BooleanMethodReturnCheckJSpecifySampleA() { + // constructor (with null return type) are not covered by the rule + return; + } + + @Nullable + public Boolean foo() { + return null; // Compliant + } + + @NullUnmarked + public Boolean bar() { + return null; // Compliant + } + + public Boolean foobar() { + return null; // Noncompliant {{Null is returned but a "Boolean" is expected.}} + } + +} + +// NullMarked at the package level +class BooleanMethodReturnCheckJSpecifySampleB { + private class Boolean { + } + + public Boolean myMethodFailing() { + return null; // Compliant + } + + public java.lang.Boolean myOtherMethod() { + class BooleanMethodReturnCheckSampleC { + private java.lang.Boolean myInnerMethod() { + return null; // Noncompliant {{Null is returned but a "Boolean" is expected.}} + } + private BooleanMethodReturnCheckSampleC foo() { + return null; // Compliant + } + } + return null; // Noncompliant {{Null is returned but a "Boolean" is expected.}} +// ^^^^ + } + + @CheckForNull + public java.lang.Boolean myMethod2() { + return null; // compliant method is annotated with @CheckForNull + } +} + +// NullMarked at the package level +class BooleanMethodReturnCheckJSpecifySampleD { + public Boolean foo() { + class BooleanMethodReturnCheckSampleE { + void bar() { + return; + } + } + Stream.of("A").forEach(a -> { + return; // Compliant + }); + return true; + } +} diff --git a/java-checks-test-sources/default/src/main/java/checks/jspecify/nullmarked/ChangeMethodContractCheck.java b/java-checks-test-sources/default/src/main/java/checks/jspecify/nullmarked/ChangeMethodContractCheck.java new file mode 100644 index 00000000000..adf2f0c749b --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/jspecify/nullmarked/ChangeMethodContractCheck.java @@ -0,0 +1,22 @@ +package checks.jspecify.nullmarked; + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.NullUnmarked; + +// NullMarked at the package level +class ChangeMethodContractCheck { + + @interface MyAnnotation {} + + @NullUnmarked + String annotatedUnmarked(Object a) { return null; } +} + +class ChangeMethodContractCheck_B extends ChangeMethodContractCheck { + + @NullMarked + @Override + String annotatedUnmarked(Object a) { return null; } // Noncompliant {{Fix the incompatibility of the annotation @NullMarked to honor @NullUnmarked of the overridden method.}} + +} + diff --git a/java-checks-test-sources/default/src/main/java/checks/jspecify/nullmarked/EqualsParametersMarkedNonNullCheck.java b/java-checks-test-sources/default/src/main/java/checks/jspecify/nullmarked/EqualsParametersMarkedNonNullCheck.java new file mode 100644 index 00000000000..5a8d57653ca --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/jspecify/nullmarked/EqualsParametersMarkedNonNullCheck.java @@ -0,0 +1,22 @@ +package checks.jspecify.nullmarked; + +import org.jspecify.annotations.NullUnmarked; + +// NullMarked at the package level +class EqualsParametersMarkedNonNullCheckSampleA { + + // @NullUnmarked not applicable to parameter + public boolean equals(Object obj) { // Compliant + return true; + } +} + +// NullMarked at the package level +class EqualsParametersMarkedNonNullCheckSampleB { + + @NullUnmarked + // @NullUnmarked not applicable to parameter + public boolean equals(Object obj) { // Compliant + return true; + } +} diff --git a/java-checks-test-sources/default/src/main/java/checks/jspecify/nullmarked/NullShouldNotBeUsedWithOptionalCheck_guava.java b/java-checks-test-sources/default/src/main/java/checks/jspecify/nullmarked/NullShouldNotBeUsedWithOptionalCheck_guava.java new file mode 100644 index 00000000000..c1809d2652d --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/jspecify/nullmarked/NullShouldNotBeUsedWithOptionalCheck_guava.java @@ -0,0 +1,114 @@ +package checks.jspecify.nullmarked; +// To help keep "guava" and "jdk" tests in sync, this file is identical to its counterpart except for the import of class "Optional" + +import com.google.common.base.Optional; +import java.util.List; +import javax.annotation.Nullable; +import org.jspecify.annotations.NullUnmarked; + +// NullMarked at the package level +interface NullShouldNotBeUsedWithOptionalCheck_guava { + + @NullUnmarked // Noncompliant {{Methods with an "Optional" return type should not be "@NullUnmarked".}} +//^^^^^^^^^^^^^ + public Optional getOptionalKo(); + +} + +// NullMarked at the package level +class NullShouldNotBeUsedWithOptionalCheck_guavaClassA { + + public NullShouldNotBeUsedWithOptionalCheck_guavaClassA() { + } + + @NullUnmarked // Noncompliant {{Methods with an "Optional" return type should not be "@NullUnmarked".}} +//^^^^^^^^^^^^^ + public Optional getOptionalKo() { + return null; // Noncompliant {{Methods with an "Optional" return type should never return null.}} +// ^^^^ + } + + public Optional getOptionalOk() { + return Optional.of("hello"); + } + + public Object doSomething1() { + return null; + } + + public Optional doSomething2() { + Worker x = new Worker() { + public String work() { + return null; + } + }; + return Optional.of("hello"); + } + + public int doSomething3(Optional arg) { + if (arg == null) { // Noncompliant {{Ensure this "Optional" could never be null and remove this null-check.}} +// ^^^^^^^^^^^ + return 0; + } + + Optional optional = getOptionalOk(); + if (optional == null) { // Noncompliant {{Ensure this "Optional" could never be null and remove this null-check.}} +// ^^^^^^^^^^^^^^^^ + return 0; + } else if (null != optional) { // Noncompliant {{Ensure this "Optional" could never be null and remove this null-check.}} +// ^^^^^^^^^^^^^^^^ + return 0; + } + + Optional optional2 = getOptionalOk(); + if (optional == optional2) { + return 0; + } else if (null == null) { + return 0; + } + + Optional optional3 = getOptionalOk(); + return optional3 == null ? 0 : 1; // Noncompliant {{Ensure this "Optional" could never be null and remove this null-check.}} +// ^^^^^^^^^^^^^^^^^ + } + + public Optional doSomething4(List myList) { + myList.stream().map(s -> { + if (s.length() > 0) { + return null; + } + return s; + }); + return Optional.of("hello"); + } + + @Deprecated + public Optional doSomething5(List myList) { + return myList.isEmpty() ? Optional.of("hello") : null; // Noncompliant {{Methods with an "Optional" return type should never return null.}} +// ^^^^ + } + + @Nullable // Noncompliant {{"Optional" variables should not be "@Nullable".}} +//^^^^^^^^^ + private Optional field; + + public void doSomething6(@Nullable Optional arg) { // Noncompliant {{"Optional" variables should not be "@Nullable".}} +// ^^^^^^^^^ + } + + public void doSomething7() { + @Nullable // Noncompliant {{"Optional" variables should not be "@Nullable".}} +// ^^^^^^^^^ + Optional var; + } + + public Optional doSomething8(boolean b) { + Object obj = b ? null : new Object(); + return Optional.of("hello"); + } + + interface Worker { + String work(); + } + +} diff --git a/java-checks-test-sources/default/src/main/java/checks/jspecify/nullmarked/NullShouldNotBeUsedWithOptionalCheck_jdk.java b/java-checks-test-sources/default/src/main/java/checks/jspecify/nullmarked/NullShouldNotBeUsedWithOptionalCheck_jdk.java new file mode 100644 index 00000000000..6ec4a775988 --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/jspecify/nullmarked/NullShouldNotBeUsedWithOptionalCheck_jdk.java @@ -0,0 +1,150 @@ +package checks.jspecify.nullmarked; +// To help keep "guava" and "jdk" tests in sync, this file is identical to its counterpart except for the import of class "Optional" + +import java.util.List; +import java.util.Optional; +import javax.annotation.Nullable; +import javax.annotation.meta.When; +import org.jspecify.annotations.NullUnmarked; + +// NullMarked at the package level +interface NullShouldNotBeUsedWithOptionalCheck_jdk { + + @NullUnmarked // Noncompliant {{Methods with an "Optional" return type should not be "@NullUnmarked".}} +//^^^^^^^^^^^^^ + public Optional getOptionalKo(); + +} + +// NullMarked at the package level +class NullShouldNotBeUsedWithOptionalCheck_jdkClassA { + + public NullShouldNotBeUsedWithOptionalCheck_jdkClassA() { + } + + @NullUnmarked // Noncompliant {{Methods with an "Optional" return type should not be "@NullUnmarked".}} +//^^^^^^^^^^^^^ + public Optional getOptionalKo() { + return null; // Noncompliant {{Methods with an "Optional" return type should never return null.}} +// ^^^^ + } + + public Optional getOptionalOk() { + return Optional.of("hello"); + } + + public Object doSomething1() { + return null; + } + + public Optional doSomething2() { + Worker x = new Worker() { + public String work() { + return null; + } + }; + return Optional.of("hello"); + } + + public int doSomething3(Optional arg) { + if (arg == null) { // Noncompliant {{Ensure this "Optional" could never be null and remove this null-check.}} +// ^^^^^^^^^^^ + return 0; + } + + Optional optional = getOptionalOk(); + if (optional == null) { // Noncompliant {{Ensure this "Optional" could never be null and remove this null-check.}} +// ^^^^^^^^^^^^^^^^ + return 0; + } else if (null != optional) { // Noncompliant {{Ensure this "Optional" could never be null and remove this null-check.}} +// ^^^^^^^^^^^^^^^^ + return 0; + } + + Optional optional2 = null; // Noncompliant {{Replace this null literal by an "Optional" object.}} +// ^^^^ + String notOptional = null; // Compliant + optional = null; // Noncompliant {{Replace this null literal by an "Optional" object.}} +// ^^^^ + optional = Optional.empty(); // Compliant + notOptional = null; // Compliant + if (optional == optional2) { + return 0; + } else if (null == null) { + return 0; + } + + Optional optional3 = getOptionalOk(); + return optional3 == null ? 0 : 1; // Noncompliant {{Ensure this "Optional" could never be null and remove this null-check.}} +// ^^^^^^^^^^^^^^^^^ + } + + public Optional doSomething4(List myList) { + myList.stream().map(s -> { + if (s.length() > 0) { + return null; + } + return s; + }); + return Optional.of("hello"); + } + + @Deprecated + public Optional doSomething5(List myList) { + return myList.isEmpty() ? Optional.of("hello") : null; // Noncompliant {{Methods with an "Optional" return type should never return null.}} +// ^^^^ + } + + @Nullable // Noncompliant {{"Optional" variables should not be "@Nullable".}} +//^^^^^^^^^ + private Optional field = null; // Noncompliant {{Replace this null literal by an "Optional" object.}} +// ^^^^ + + public void doSomething6(@Nullable Optional arg) { // Noncompliant {{"Optional" variables should not be "@Nullable".}} +// ^^^^^^^^^ + } + + public void doSomething7() { + @Nullable // Noncompliant {{"Optional" variables should not be "@Nullable".}} +// ^^^^^^^^^ + Optional var; + } + + public void NonnullWithArgument1() { + @javax.annotation.Nonnull(when= When.MAYBE) // Noncompliant {{"Optional" variables should not be "@Nonnull(when=MAYBE)".}} +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Optional var; + } + + public void NonnullWithArgument2() { + @javax.annotation.Nonnull(when= When.NEVER) // Noncompliant {{"Optional" variables should not be "@Nonnull(when=NEVER)".}} +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Optional var; + } + + public void NonnullWithArgument3() { + @javax.annotation.Nonnull(when= When.UNKNOWN) // Noncompliant {{"Optional" variables should not be "@Nonnull(when=UNKNOWN)".}} +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Optional var; + } + + public void NonnullWithArgument4() { + @javax.annotation.Nonnull(when= When.ALWAYS) // Compliant: when=ALWAYS is Nonnull + Optional var; + } + + public void NonnullWithArgument5() { + @javax.annotation.Nonnull() // Compliant + Optional var; + } + + public Optional doSomething8(boolean b) { + Object obj = b ? null : new Object(); + return Optional.of("hello"); + } + + interface Worker { + String work(); + } + +} diff --git a/java-checks-test-sources/default/src/main/java/checks/jspecify/nullmarked/PrimitivesMarkedNullableCheck.java b/java-checks-test-sources/default/src/main/java/checks/jspecify/nullmarked/PrimitivesMarkedNullableCheck.java new file mode 100644 index 00000000000..40d3d32e724 --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/jspecify/nullmarked/PrimitivesMarkedNullableCheck.java @@ -0,0 +1,72 @@ +package checks.jspecify.nullmarked; + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.NullUnmarked; + +@interface AnotherAnnotation { +} + +// NullMarked at the package level +@interface MyCheckForNull { +} + +// NullMarked at the package level +abstract class PrimitivesMarkedNullableCheckSample { + + abstract int getInt1(); + + @AnotherAnnotation + abstract int getInt2(); + + @NullUnmarked + protected abstract boolean isBool(); // Noncompliant {{"@NullUnmarked" annotation should not be used on primitive types}} [[quickfixes=qf1]] +// ^^^^^^^ + // fix@qf1 {{Remove "@NullUnmarked"}} + // edit@qf1 [[sl=-1;el=+0;sc=3;ec=3]] {{}} + + @NullUnmarked + public double getDouble1() { return 0.0; } // Noncompliant {{"@NullUnmarked" annotation should not be used on primitive types}} + + public double getDouble2() { return 0.0; } + + @MyCheckForNull + public double getDouble2_1() { return 0.0; } // Compliant, Nullable meta annotation are not taken into account + + @NullMarked + public double getDouble2_2() { return 0.0; } // Compliant, Nonnull is useless, but is accepted as it can be added for consistency + + @NullUnmarked + public Double getDouble3() { return 0.0; } + + @NullUnmarked + public Double getDouble4() { return 0.0; } + + @NullUnmarked + public Object getObj0() { return null; } + + @NullUnmarked + public Object getObj1() { return null; } + + public Object getObj2() { return null; } + + protected abstract @NullUnmarked boolean isBool2(); // Noncompliant {{"@NullUnmarked" annotation should not be used on primitive types}} [[quickfixes=qf3]] +// ^^^^^^^ + // fix@qf3 {{Remove "@NullUnmarked"}} + // edit@qf3 [[sc=22;ec=36]] {{}} + + @NullUnmarked + Object containsAnonymousClass() { + return new PrimitivesMarkedNullableCheckParent() { + int getInt0() { + return 0; + } + }; + } + +} + +abstract class PrimitivesMarkedNullableCheckNullMarkedChild extends PrimitivesMarkedNullableCheckParent { + + abstract int getInt0(); // Compliant, not directly marked as CheckForNull, an issue will be on the parent if needed. + +} diff --git a/java-checks-test-sources/default/src/main/java/checks/jspecify/nullmarked/PrimitivesMarkedNullableCheckParent.java b/java-checks-test-sources/default/src/main/java/checks/jspecify/nullmarked/PrimitivesMarkedNullableCheckParent.java new file mode 100644 index 00000000000..a6bdea0f9bf --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/jspecify/nullmarked/PrimitivesMarkedNullableCheckParent.java @@ -0,0 +1,10 @@ +package checks.jspecify.nullmarked; + +import org.jspecify.annotations.NullMarked; + +abstract class PrimitivesMarkedNullableCheckParent { + + @NullMarked + abstract int getInt0(); // Noncompliant + +} diff --git a/java-checks-test-sources/default/src/main/java/checks/jspecify/nullmarked/ReturnEmptyArrayNotNullCheck.java b/java-checks-test-sources/default/src/main/java/checks/jspecify/nullmarked/ReturnEmptyArrayNotNullCheck.java new file mode 100644 index 00000000000..d6766c1cecd --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/jspecify/nullmarked/ReturnEmptyArrayNotNullCheck.java @@ -0,0 +1,192 @@ +package checks.jspecify.nullmarked; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import javax.annotation.CheckForNull; +import org.jspecify.annotations.NullUnmarked; +import org.springframework.batch.item.ItemProcessor; + +// NullMarked at the package level +class ReturnEmptyArrayNotNullCheckSampleA { + + public ReturnEmptyArrayNotNullCheckSampleA() { + return; + } + + public void f1() { + return; + } + + public int[] f2() { + return null; // Noncompliant {{Return an empty array instead of null.}} +// ^^^^ + } + + public Object f3() { + return null; // Compliant as not an array + } + + public Object f4()[] { + return null; // Noncompliant + } + + public int[] f5(boolean cond) { + new ReturnEmptyArrayNotNullCheckSampleB() { + public Object g1() { + return null; + } + + public int[] g2() { + return null; // Noncompliant + } + }; + if (cond) { + return new int[0]; + } + return null; // Noncompliant + } + + public List f6() { + return null; // Noncompliant + } + + public ArrayList f7() { + return null; // Noncompliant + } + + public Set f8(boolean cond) { + if (cond) { + return Collections.EMPTY_SET; + } + return null; // Noncompliant {{Return an empty collection instead of null.}} + } + + public List[] f9() { + return null; // Noncompliant {{Return an empty array instead of null.}} + } + + public java.util.Collection f10() { + return null; // Noncompliant + } +} + +// NullMarked at the package level +interface ReturnEmptyArrayNotNullCheckSampleB{ + default int[] a(){ + return null; // Noncompliant + } + + default int[] b(){ + return new int[4]; + } + + default List c(){ + return null; // Noncompliant + } + + default List d(){ + return new ArrayList(); + } + + default int[] e(){ + return null; // Noncompliant + } + + default int[] f(){ + return new int[4]; + } +} + +// NullMarked at the package level +class ReturnEmptyArrayNotNullCheckSampleC { + @SuppressWarnings("Something") + public int[] gul() { + return null; // Noncompliant + } + + @NullUnmarked + public Object foo() { + return null; + } + + @CheckForNull + public Object bar() { + return null; + } + + @NullUnmarked + public int[] fool() { + return null; + } + + @CheckForNull + public int[] bark() { + return null; + } + + int[] qix(){ + takeLambda(a -> { + return null; + }); + return new int[1]; + } + + static final Object CONSTANT = takeLambda(a->{return null;}); + + private static Object takeLambda(Function o) { + return o.apply(""); + } +} + +// NullMarked at the package level +class ReturnEmptyArrayNotNullCheckSampleD implements ItemProcessor> { + @Override + public List process(Integer i) { + return null; // Compliant: ItemProcessor requires to return null value to stop the processing + } + + public List process2(Integer i) { + return null; // Noncompliant + } +} + +// NullMarked at the package level +interface ReturnEmptyArrayNotNullCheckSampleE { + List bar(); +} + +// NullMarked at the package level +class ReturnEmptyArrayNotNullCheckSampleF implements ReturnEmptyArrayNotNullCheckSampleE { + @Override + public List bar() { + return null; // Noncompliant + } +} + +// NullMarked at the package level +class ReturnEmptyArrayNotNullCheckSampleG implements ItemProcessor>, ReturnEmptyArrayNotNullCheckSampleE { + @Override + public List bar() { + return null; // Noncompliant + } + + @Override + public List process(Integer integer) throws Exception { + return Collections.emptyList(); + } +} + +// NullMarked at the package level +class ReturnEmptyArrayNotNullCheckSampleH implements ItemProcessor { + @Override + public Integer[] process(Integer a) { + return null; // Compliant + } + + public int[] process(String a) { + return null; // Noncompliant + } +} diff --git a/java-checks-test-sources/default/src/main/java/checks/jspecify/nullmarked/package-info.java b/java-checks-test-sources/default/src/main/java/checks/jspecify/nullmarked/package-info.java new file mode 100644 index 00000000000..bdc4d4c8472 --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/jspecify/nullmarked/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package checks.jspecify.nullmarked; + +import org.jspecify.annotations.NullMarked; diff --git a/java-checks/src/test/java/org/sonar/java/checks/BooleanMethodReturnCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/BooleanMethodReturnCheckTest.java index 1890ee87213..808a629e4b2 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/BooleanMethodReturnCheckTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/BooleanMethodReturnCheckTest.java @@ -41,4 +41,17 @@ void test_non_compiling() { .verifyNoIssues(); } + + @Test + void test_jspecify_null_marked() { + CheckVerifier.newVerifier() + .onFile(TestUtils.mainCodeSourcesPath("checks/jspecify/nullmarked/BooleanMethodReturnCheck.java")) + .withCheck(new BooleanMethodReturnCheck()) + .verifyIssues(); + CheckVerifier.newVerifier() + .onFile(TestUtils.mainCodeSourcesPath("checks/jspecify/BooleanMethodReturnCheckNullMarked.java")) + .withCheck(new BooleanMethodReturnCheck()) + .verifyIssues(); + } + } diff --git a/java-checks/src/test/java/org/sonar/java/checks/ChangeMethodContractCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/ChangeMethodContractCheckTest.java index 82d760264da..fd7a0154b1f 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/ChangeMethodContractCheckTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/ChangeMethodContractCheckTest.java @@ -51,6 +51,18 @@ void test_package_level_annotations_nullable_api() { .verifyIssues(); } + @Test + void test_jspecify_null_marked() { + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/jspecify/ChangeMethodContractCheckNullMarked.java")) + .withCheck(new ChangeMethodContractCheck()) + .verifyIssues(); + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/jspecify/nullmarked/ChangeMethodContractCheck.java")) + .withCheck(new ChangeMethodContractCheck()) + .verifyIssues(); + } + @Test void non_compiling() { CheckVerifier.newVerifier() diff --git a/java-checks/src/test/java/org/sonar/java/checks/EqualsParametersMarkedNonNullCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/EqualsParametersMarkedNonNullCheckTest.java index bef56ef712d..3b757ec042a 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/EqualsParametersMarkedNonNullCheckTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/EqualsParametersMarkedNonNullCheckTest.java @@ -33,4 +33,17 @@ void detected() { .withCheck(new EqualsParametersMarkedNonNullCheck()) .verifyIssues(); } + + @Test + void test_jspecify_null_marked() { + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/jspecify/EqualsParametersMarkedNonNullCheckNullMarked.java")) + .withCheck(new EqualsParametersMarkedNonNullCheck()) + .verifyNoIssues(); + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/jspecify/nullmarked/EqualsParametersMarkedNonNullCheck.java")) + .withCheck(new EqualsParametersMarkedNonNullCheck()) + .verifyNoIssues(); + } + } diff --git a/java-checks/src/test/java/org/sonar/java/checks/NullShouldNotBeUsedWithOptionalCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/NullShouldNotBeUsedWithOptionalCheckTest.java index 7897e32c29d..eaba7f68ee5 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/NullShouldNotBeUsedWithOptionalCheckTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/NullShouldNotBeUsedWithOptionalCheckTest.java @@ -39,4 +39,25 @@ void test() { .verifyIssues(); } + @Test + void test_jspecify_null_marked() { + NullShouldNotBeUsedWithOptionalCheck check = new NullShouldNotBeUsedWithOptionalCheck(); + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/jspecify/NullShouldNotBeUsedWithOptionalCheckNullMarked_jdk.java")) + .withCheck(check) + .verifyIssues(); + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/jspecify/NullShouldNotBeUsedWithOptionalCheckNullMarked_guava.java")) + .withCheck(check) + .verifyIssues(); + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/jspecify/nullmarked/NullShouldNotBeUsedWithOptionalCheck_jdk.java")) + .withCheck(check) + .verifyIssues(); + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/jspecify/nullmarked/NullShouldNotBeUsedWithOptionalCheck_guava.java")) + .withCheck(check) + .verifyIssues(); + } + } diff --git a/java-checks/src/test/java/org/sonar/java/checks/PrimitivesMarkedNullableCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/PrimitivesMarkedNullableCheckTest.java index 43b138b9793..c9487650175 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/PrimitivesMarkedNullableCheckTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/PrimitivesMarkedNullableCheckTest.java @@ -43,4 +43,16 @@ void noSemantic() { .verifyNoIssues(); } + @Test + void test_jspecify_null_marked() { + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/jspecify/PrimitivesMarkedNullableCheckNullMarked.java")) + .withCheck(new PrimitivesMarkedNullableCheck()) + .verifyIssues(); + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/jspecify/nullmarked/PrimitivesMarkedNullableCheck.java")) + .withCheck(new PrimitivesMarkedNullableCheck()) + .verifyIssues(); + } + } diff --git a/java-checks/src/test/java/org/sonar/java/checks/ReturnEmptyArrayNotNullCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/ReturnEmptyArrayNotNullCheckTest.java index bef057167cb..2a23bdde5fa 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/ReturnEmptyArrayNotNullCheckTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/ReturnEmptyArrayNotNullCheckTest.java @@ -46,6 +46,19 @@ void quick_fixes() { .verifyIssues(); } + @Test + void test_jspecify_nullmarked() { + CheckVerifier.newVerifier() + .onFile(TestUtils.mainCodeSourcesPath("checks/jspecify/ReturnEmptyArrayNotNullCheckNullMarked.java")) + .withCheck(new ReturnEmptyArrayNotNullCheck()) + .verifyIssues(); + + CheckVerifier.newVerifier() + .onFile(TestUtils.mainCodeSourcesPath("checks/jspecify/nullmarked/ReturnEmptyArrayNotNullCheck.java")) + .withCheck(new ReturnEmptyArrayNotNullCheck()) + .verifyIssues(); + } + @Test void test_non_compiling() { CheckVerifier.newVerifier() diff --git a/java-frontend/src/main/java/org/sonar/java/model/JSymbolMetadataNullabilityHelper.java b/java-frontend/src/main/java/org/sonar/java/model/JSymbolMetadataNullabilityHelper.java index 058cd002efd..a888eb9a947 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JSymbolMetadataNullabilityHelper.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JSymbolMetadataNullabilityHelper.java @@ -147,6 +147,12 @@ private JSymbolMetadataNullabilityHelper() { private static final String JAKARTA_ANNOTATION_NONNULL = "jakarta.annotation.Nonnull"; + /** + * JSpecify set of Standard Annotations for Java Static Analysis. + */ + private static final String ORG_JSPECIFY_ANNOTATIONS_NULL_MARKED = "org.jspecify.annotations.NullMarked"; + private static final String ORG_JSPECIFY_ANNOTATIONS_NULL_UNMARKED = "org.jspecify.annotations.NullUnmarked"; + /** * Target parameters and return values. * Only applicable to package. @@ -218,6 +224,11 @@ private JSymbolMetadataNullabilityHelper() { configureAnnotation(ORG_SPRINGFRAMEWORK_LANG_NON_NULL_API, NON_NULL, Arrays.asList(METHOD, PARAMETER), Collections.singletonList(PACKAGE)); + configureAnnotation(ORG_JSPECIFY_ANNOTATIONS_NULL_MARKED, NON_NULL, + Arrays.asList(FIELD, METHOD, PARAMETER), Arrays.asList(NullabilityLevel.METHOD, CLASS, PACKAGE)); + configureAnnotation(ORG_JSPECIFY_ANNOTATIONS_NULL_UNMARKED, WEAK_NULLABLE, + Arrays.asList(FIELD, METHOD, PARAMETER), Arrays.asList(NullabilityLevel.METHOD, CLASS, PACKAGE)); + configureAnnotation(JAVAX_ANNOTATION_PARAMETERS_ARE_NONNULL_BY_DEFAULT, NON_NULL, Collections.singletonList(PARAMETER), Arrays.asList(NullabilityLevel.METHOD, CLASS, PACKAGE)); configureAnnotation(JAVAX_ANNOTATION_PARAMETERS_ARE_NULLABLE_BY_DEFAULT, WEAK_NULLABLE, @@ -250,6 +261,8 @@ private JSymbolMetadataNullabilityHelper() { KNOWN_ANNOTATIONS.add(JAKARTA_ANNOTATION_PARAMETERS_ARE_NULLABLE_BY_DEFAULT); KNOWN_ANNOTATIONS.add(ORG_SPRINGFRAMEWORK_LANG_NON_NULL_FIELDS); KNOWN_ANNOTATIONS.add(ORG_ECLIPSE_JDT_ANNOTATION_NON_NULL_BY_DEFAULT); + KNOWN_ANNOTATIONS.add(ORG_JSPECIFY_ANNOTATIONS_NULL_MARKED); + KNOWN_ANNOTATIONS.add(ORG_JSPECIFY_ANNOTATIONS_NULL_UNMARKED); } private static void configureAnnotation(String name, NullabilityType type, List targets, List levels) {