Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Annotation-based null analysis - warning appears only with array parameter

I am getting the following (confusing) warning when using annotation-based null analysis where an array is involved:

Null type safety (type annotations): The expression of type 'int[]' needs unchecked conversion to conform to 'int @Nullable[]'

This occurs when I pass an unannotated int[] to an int @Nullable[] parameter.

This is surprising. Normally it is only a problem if you take a nullable value and try to pass it to a nonnull parameter, but I am doing the opposite - taking a known non-null (though not annotated) array and passing it to a method (Arrays.equals) which does accept nulls.

Also it does not seem to be a problem with non-array objects. Normally a variable or return of (unannotated, non-array) type T can be assigned to a @Nullable T.

So my question is why does this change when T is an array type?


My class is a hashable/equality-comparable proxy for a native class that uses an int[] (taken from a C++ function) as a unique identifier. My package uses annotation-based null analysis and the 3rd-party package (with the native class) does not.

Here's a cut-down version that shows the problem:

TagKey.java:

package example;

import java.util.Arrays;

import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;

import other.Tag;

import com.google.common.collect.Interner;
import com.google.common.collect.Interners;

public class TagKey {

    private TagKey(Tag tag) {
        this.tag = tag;
    }

    public static TagKey forTag(Tag tag) {
        TagKey candidateKey = new TagKey(tag);
        return INTERNER.intern(candidateKey);
    }

    @Override
    public int hashCode() {
        return Arrays.hashCode(this.tag.getUniqueIdentifier());
    }

    @Override
    public boolean equals(@Nullable Object obj) {
        if (this == obj) {
            return true;
        } else if (obj == null) {
            return false;
        } else if (getClass() != obj.getClass()) {
            return false;
        } else {
            return ((TagKey) obj).hasMatchingIdentifier(this.tag.getUniqueIdentifier()); // Warning appears here
        }
    }

    public boolean hasMatchingIdentifier(int @Nullable[] id) {
        return Arrays.equals(this.tag.getUniqueIdentifier(), id);
    }

    @SuppressWarnings("null") // Guava cannot use type parameter annotations due to backward compatibility with Java 6
    private static final Interner<TagKey> INTERNER = Interners.newWeakInterner();

    private final Tag tag;

}

package-info.java:

/**
 * @author finnw
 *
 */
@org.eclipse.jdt.annotation.NonNullByDefault
package example;

Tag.java: (partial)

package other;

public class Tag {
    public native int[] getUniqueIdentifier(); // Currently returns a fresh array on each call, but may change in the near future to reuse a single array
}

The workaround I am currently using:

    public boolean hasMatchingIdentifier(@Nullable Object id) {
        return id instanceof int[] &&
               Arrays.equals(this.tag.getUniqueIdentifier(), (int[])id);
    }

I would prefer to avoid these approaches:

  • Adding @SuppressWarnings("null") to TagKey or its equals method (The production version is somewhat more complex and has a high risk of embarrassing NPEs.)

Notes:

  • My JDT version is 3.10.0.v20140606-1215
  • Earlier I made the mistake of declaring @Nullable int[] id. Surprisingly there was no warning message for this, even though it implies that the primitive int elements may be null which is clearly wrong.
like image 584
finnw Avatar asked Nov 11 '22 02:11

finnw


1 Answers

Add this method:

@SuppressWarnings("null")
public static int @Nullable[] getUniqueIdentifier(Tag tag) {
    return tag.getUniqueIdentifier();
}

then:

return ((TagKey) obj).hasMatchingIdentifier(getUniqueIdentifier(this.tag));

This is why I'm ignoring the "unchecked conversion from non-annotated" warning until nullity profiles are supported, you get stuck either suppressing null warnings everywhere (which defeats the point) or making annotated wrapper methods for every library.

like image 179
Sean Van Gorder Avatar answered Nov 14 '22 21:11

Sean Van Gorder