org.checkerframework.checker.nullness.CollectionToArrayHeuristics Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of checker Show documentation
Show all versions of checker Show documentation
The Checker Framework enhances Java's type system to
make it more powerful and useful. This lets software developers
detect and prevent errors in their Java programs.
The Checker Framework includes compiler plug-ins ("checkers")
that find bugs or verify their absence. It also permits you to
write your own compiler plug-ins.
package org.checkerframework.checker.nullness;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.NewArrayTree;
import com.sun.source.tree.Tree;
import java.util.Collection;
import java.util.List;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.common.value.qual.ArrayLen;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
import org.checkerframework.framework.util.AnnotatedTypes;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.TreeUtils;
/**
* Determines the nullness type of calls to {@link java.util.Collection#toArray()}.
*
* @checker_framework.manual #nullness-collection-toarray Nullness and conversions from collections
* to arrays
* @checker_framework.manual #constant-value-checker Constant Value Checker
*/
public class CollectionToArrayHeuristics {
/** The processing environment. */
private final ProcessingEnvironment processingEnv;
/** The checker, used for issuing diagnostic messages. */
private final BaseTypeChecker checker;
/** The type factory. */
private final NullnessAnnotatedTypeFactory atypeFactory;
/** Whether to trust {@code @ArrayLen(0)} annotations. */
private final boolean trustArrayLenZero;
/** The Collection type. */
private final AnnotatedDeclaredType collectionType;
/** The Collection.toArray(T[]) method. */
private final ExecutableElement collectionToArrayE;
/** The Collection.size() method. */
private final ExecutableElement size;
/** The ArrayLen.value field/element. */
private final ExecutableElement arrayLenValueElement;
/**
* Create a CollectionToArrayHeuristics.
*
* @param checker the checker, used for issuing diagnostic messages
* @param factory the type factory
*/
public CollectionToArrayHeuristics(
BaseTypeChecker checker, NullnessAnnotatedTypeFactory factory) {
this.processingEnv = checker.getProcessingEnvironment();
this.checker = checker;
this.atypeFactory = factory;
this.collectionType =
factory.fromElement(ElementUtils.getTypeElement(processingEnv, Collection.class));
this.collectionToArrayE =
TreeUtils.getMethod("java.util.Collection", "toArray", processingEnv, "T[]");
this.size = TreeUtils.getMethod("java.util.Collection", "size", 0, processingEnv);
this.arrayLenValueElement = TreeUtils.getMethod(ArrayLen.class, "value", 0, processingEnv);
this.trustArrayLenZero =
checker.getLintOption(
NullnessChecker.LINT_TRUSTARRAYLENZERO, NullnessChecker.LINT_DEFAULT_TRUSTARRAYLENZERO);
}
/**
* If the method invocation is a call to {@code toArray}, then it manipulates the returned type of
* {@code method} arg to contain the appropriate nullness. Otherwise, it does nothing.
*
* @param tree method invocation tree
* @param method invoked method type
*/
public void handle(MethodInvocationTree tree, AnnotatedExecutableType method) {
if (TreeUtils.isMethodInvocation(tree, collectionToArrayE, processingEnv)) {
assert !tree.getArguments().isEmpty() : tree;
ExpressionTree argument = tree.getArguments().get(0);
boolean receiverIsNonNull = receiverIsCollectionOfNonNullElements(tree);
boolean argIsHandled =
isHandledArrayCreation(argument, receiverName(tree.getMethodSelect()))
|| (trustArrayLenZero && isArrayLenZeroFieldAccess(argument));
setComponentNullness(receiverIsNonNull && argIsHandled, method.getReturnType());
// TODO: We need a mechanism to prevent nullable collections
// from inserting null elements into a nonnull arrays.
if (!receiverIsNonNull) {
setComponentNullness(false, method.getParameterTypes().get(0));
}
if (receiverIsNonNull && !argIsHandled) {
if (argument.getKind() != Tree.Kind.NEW_ARRAY) {
checker.reportWarning(tree, "toarray.nullable.elements.not.newarray");
} else {
checker.reportWarning(tree, "toarray.nullable.elements.mismatched.size");
}
}
}
}
/**
* Sets the nullness of the component of the array type.
*
* @param isNonNull indicates which annotation ({@code NonNull} or {@code Nullable}) should be
* inserted
* @param type the array type
*/
private void setComponentNullness(boolean isNonNull, AnnotatedTypeMirror type) {
assert type.getKind() == TypeKind.ARRAY;
AnnotatedTypeMirror compType = ((AnnotatedArrayType) type).getComponentType();
compType.replaceAnnotation(isNonNull ? atypeFactory.NONNULL : atypeFactory.NULLABLE);
}
/**
* Returns true if {@code argument} is one of the array creation trees that the heuristic handles.
*
* @param argument the tree passed to {@link Collection#toArray(Object[]) Collection.toArray(T[])}
* @param receiver the expression for the receiver collection
* @return true if the argument is handled and assume to return nonnull elements
*/
private boolean isHandledArrayCreation(Tree argument, String receiver) {
if (argument.getKind() != Tree.Kind.NEW_ARRAY) {
return false;
}
NewArrayTree newArr = (NewArrayTree) argument;
// empty array initializer
if (newArr.getInitializers() != null) {
return newArr.getInitializers().isEmpty();
}
assert !newArr.getDimensions().isEmpty();
Tree dimension = newArr.getDimensions().get(newArr.getDimensions().size() - 1);
// 0-length array creation
if (dimension.toString().equals("0")) {
return true;
}
// size()-length array creation
if (TreeUtils.isMethodInvocation(dimension, size, processingEnv)) {
MethodInvocationTree invok = (MethodInvocationTree) dimension;
String invokReceiver = receiverName(invok.getMethodSelect());
return invokReceiver.equals(receiver);
}
return false;
}
/**
* Returns true if the argument is a field access expression, where the field has declared type
* {@code @ArrayLen(0)}.
*
* @param argument an expression tree
* @return true if the argument is a field access expression, where the field has declared type
* {@code @ArrayLen(0)}
*/
private boolean isArrayLenZeroFieldAccess(ExpressionTree argument) {
Element el = TreeUtils.elementFromUse(argument);
if (el != null && el.getKind().isField()) {
TypeMirror t = ElementUtils.getType(el);
if (t.getKind() == TypeKind.ARRAY) {
List extends AnnotationMirror> ams = t.getAnnotationMirrors();
for (AnnotationMirror am : ams) {
if (atypeFactory.areSameByClass(am, ArrayLen.class)) {
List lens =
AnnotationUtils.getElementValueArray(am, arrayLenValueElement, Integer.class);
if (lens.size() == 1 && lens.get(0) == 0) {
return true;
}
}
}
}
}
return false;
}
/**
* Returns {@code true} if the method invocation tree receiver is collection that contains
* non-null elements (i.e. its type argument is {@code @NonNull}.
*
* @param tree a method invocation
* @return true if the receiver is a collection of non-null elements
*/
private boolean receiverIsCollectionOfNonNullElements(MethodInvocationTree tree) {
// check receiver
AnnotatedTypeMirror receiver = atypeFactory.getReceiverType(tree);
AnnotatedDeclaredType collection =
AnnotatedTypes.asSuper(atypeFactory, receiver, collectionType);
if (collection.getTypeArguments().isEmpty() // raw type
|| !collection.getTypeArguments().get(0).hasEffectiveAnnotation(atypeFactory.NONNULL)) {
return false;
}
return true;
}
/**
* The name of the receiver object of the tree.
*
* @param tree either an identifier tree or a member select tree
*/
// This method is quite sloppy, but works most of the time
private String receiverName(Tree tree) {
if (tree.getKind() == Tree.Kind.MEMBER_SELECT) {
return ((MemberSelectTree) tree).getExpression().toString();
} else {
return "this";
}
}
}