“@NonNull” values should not be set to null (RSPEC-2637)
Why do you believe it’s a false-positive/false-negative?
In code that is @NullMarked with JSpecify, a method signature of <T> T guaranteeNonNull(@Nullable T value) unambiguously states that the return value will never be null. Yet for some reason, S2637 claims that the method can return null.
Product
SonarQube for IDE 10.25.0.81504 in IntelliJ, not connected to any SonarQube
How can we reproduce the problem? Give us a self-contained snippet of code (formatted text, no screenshots)
See the two classes below.
Notes
The problem will not appear when the guaranteeNonNull() method is in the same class.
Though the FP in the code below says “@NullMarked at class level”, the issue also occurs when the annotation is placed on the package level (“@NullMarked at package level”). Presumably, it would also occur in a module.
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public class Reproducer
{
private @Nullable String foo;
public void doStuff()
{
System.out.println(getFooField());
}
private String getFooField()
{
return NullnessHelper.guaranteeNonNull(foo); // FP »This method's return value is marked "@NullMarked at class level" but null is returned.«
}
}
import lombok.experimental.UtilityClass;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@UtilityClass
@NullMarked
public class NullnessHelper
{
public static <T> T guaranteeNonNull(@Nullable T value)
{
if (value == null)
{
throw new IllegalStateException("The value is not supposed to be null at this point");
}
return value;
}
}
No, this is a different issue. Here, S2637 needs to be able to get all the information from the method signature alone: according to the JSpecify rules, <T> T guaranteeNonNull(@Nullable T value) (inside a null-marked class) clearly states it will never return null.
Note that when moving the guaranteeNonNull() method into the Reproducer class, the FP disappears, probably because SonarJava is then able to figure out what the control flow inside the method looks like.
But it shouldn’t need to do that! The guaranteeNonNull() method could just as well come from a library where the source code is not available. In fact, in my original code the FP occurs for each and every call of a guaranteeNonNull() method that indeed resides in a jar dependency. But the same thing would probably happen if the method was in an interface where we don’t know the implementation.
As I wrote above, all it takes is looking at the signature.
Personally, my guess is that the fact that one type use of T is annotated @Nullable (the one for the parameter in the signature) lets the rule assume that any use of T indicates a nullable value, hence it believes the method can return null. And though that assumption may perhaps be true for other nullness annotation libraries (I haven’t checked), for JSpecify it’s the opposite of the specified meaning.
I’ve been experimenting with your code and checking the rules code.
And what I’ve found is that the problem may come from the usage of Generics and the Nullable annotation.
If you use a non generic approach for your method then issue dissapears.
public static String guaranteeNonNull(@Nullable String value) { // No FP
if (value == null) {
throw new IllegalStateException("The value is not supposed to be null at this point");
}
return value;
}
I’m going to create a Jira ticket in order to notify the development team of this use case.