blob: f0d4c21e81315db174992e910debe8d1e073d0e5 [file] [log] [blame]
package org.checkerframework.checker.nullness;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import org.checkerframework.checker.initialization.InitializationTransfer;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.nullness.qual.PolyNull;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.dataflow.analysis.ConditionalTransferResult;
import org.checkerframework.dataflow.analysis.TransferInput;
import org.checkerframework.dataflow.analysis.TransferResult;
import org.checkerframework.dataflow.cfg.node.ArrayAccessNode;
import org.checkerframework.dataflow.cfg.node.FieldAccessNode;
import org.checkerframework.dataflow.cfg.node.InstanceOfNode;
import org.checkerframework.dataflow.cfg.node.MethodAccessNode;
import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
import org.checkerframework.dataflow.cfg.node.Node;
import org.checkerframework.dataflow.cfg.node.NullLiteralNode;
import org.checkerframework.dataflow.cfg.node.ReturnNode;
import org.checkerframework.dataflow.cfg.node.ThrowNode;
import org.checkerframework.dataflow.expression.JavaExpression;
import org.checkerframework.dataflow.expression.LocalVariable;
import org.checkerframework.framework.flow.CFAbstractAnalysis;
import org.checkerframework.framework.flow.CFAbstractStore;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
import org.checkerframework.framework.type.visitor.SimpleAnnotatedTypeScanner;
import org.checkerframework.framework.util.AnnotatedTypes;
import org.checkerframework.javacutil.AnnotationBuilder;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypesUtils;
/**
* Transfer function for the non-null type system. Performs the following refinements:
*
* <ol>
* <li>After an expression is compared with the {@code null} literal, then that expression can
* safely be considered {@link NonNull} if the result of the comparison is false or {@link
* Nullable} if the result is true.
* <li>If an expression is dereferenced, then it can safely be assumed to non-null in the future.
* If it would not be, then the dereference would have raised a {@link NullPointerException}.
* <li>Tracks whether {@link PolyNull} is known to be {@link NonNull} or {@link Nullable} (or not
* known to be either).
* </ol>
*/
public class NullnessTransfer
extends InitializationTransfer<NullnessValue, NullnessTransfer, NullnessStore> {
/** The @{@link NonNull} annotation. */
protected final AnnotationMirror NONNULL;
/** The @{@link Nullable} annotation. */
protected final AnnotationMirror NULLABLE;
/** The @{@link PolyNull} annotation. */
protected final AnnotationMirror POLYNULL;
/**
* Java's Map interface.
*
* <p>The qualifiers in this type don't matter -- it is not used as a fully-annotated
* AnnotatedDeclaredType, but just passed to asSuper().
*/
protected final AnnotatedDeclaredType MAP_TYPE;
/** The type factory for the nullness analysis that was passed to the constructor. */
protected final GenericAnnotatedTypeFactory<
NullnessValue,
NullnessStore,
NullnessTransfer,
? extends CFAbstractAnalysis<NullnessValue, NullnessStore, NullnessTransfer>>
nullnessTypeFactory;
/**
* The type factory for the map key analysis, or null if the Map Key Checker should not be run.
*/
protected final @Nullable KeyForAnnotatedTypeFactory keyForTypeFactory;
/** Create a new NullnessTransfer for the given analysis. */
public NullnessTransfer(NullnessAnalysis analysis) {
super(analysis);
this.nullnessTypeFactory = analysis.getTypeFactory();
Elements elements = nullnessTypeFactory.getElementUtils();
BaseTypeChecker checker = nullnessTypeFactory.getChecker();
if (checker.hasOption("assumeKeyFor")) {
this.keyForTypeFactory = null;
} else {
// It is error-prone to put a type factory in a field. It is OK here because
// keyForTypeFactory is used only to call methods isMapGet() and isKeyForMap().
this.keyForTypeFactory = checker.getTypeFactoryOfSubchecker(KeyForSubchecker.class);
}
NONNULL = AnnotationBuilder.fromClass(elements, NonNull.class);
NULLABLE = AnnotationBuilder.fromClass(elements, Nullable.class);
POLYNULL = AnnotationBuilder.fromClass(elements, PolyNull.class);
MAP_TYPE =
(AnnotatedDeclaredType)
AnnotatedTypeMirror.createType(
TypesUtils.typeFromClass(Map.class, analysis.getTypes(), elements),
nullnessTypeFactory,
false);
}
/**
* Sets a given {@link Node} to non-null in the given {@code store}. Calls to this method
* implement case 2.
*
* @param store the store to update
* @param node the node that should be non-null
*/
protected void makeNonNull(NullnessStore store, Node node) {
JavaExpression internalRepr = JavaExpression.fromNode(node);
store.insertValue(internalRepr, NONNULL);
}
/** Sets a given {@link Node} {@code node} to non-null in the given {@link TransferResult}. */
protected void makeNonNull(TransferResult<NullnessValue, NullnessStore> result, Node node) {
if (result.containsTwoStores()) {
makeNonNull(result.getThenStore(), node);
makeNonNull(result.getElseStore(), node);
} else {
makeNonNull(result.getRegularStore(), node);
}
}
/** Refine the given result to @NonNull. */
protected void refineToNonNull(TransferResult<NullnessValue, NullnessStore> result) {
NullnessValue oldResultValue = result.getResultValue();
NullnessValue refinedResultValue =
analysis.createSingleAnnotationValue(NONNULL, oldResultValue.getUnderlyingType());
NullnessValue newResultValue = refinedResultValue.mostSpecific(oldResultValue, null);
result.setResultValue(newResultValue);
}
@Override
protected @Nullable NullnessValue finishValue(
@Nullable NullnessValue value, NullnessStore store) {
value = super.finishValue(value, store);
if (value != null) {
value.isPolyNullNonNull = store.isPolyNullNonNull();
value.isPolyNullNull = store.isPolyNullNull();
}
return value;
}
@Override
protected @Nullable NullnessValue finishValue(
@Nullable NullnessValue value, NullnessStore thenStore, NullnessStore elseStore) {
value = super.finishValue(value, thenStore, elseStore);
if (value != null) {
value.isPolyNullNonNull = thenStore.isPolyNullNonNull() && elseStore.isPolyNullNonNull();
value.isPolyNullNull = thenStore.isPolyNullNull() && elseStore.isPolyNullNull();
}
return value;
}
/**
* {@inheritDoc}
*
* <p>Furthermore, this method refines the type to {@code NonNull} for the appropriate branch if
* an expression is compared to the {@code null} literal (listed as case 1 in the class
* description).
*/
@Override
protected TransferResult<NullnessValue, NullnessStore> strengthenAnnotationOfEqualTo(
TransferResult<NullnessValue, NullnessStore> res,
Node firstNode,
Node secondNode,
NullnessValue firstValue,
NullnessValue secondValue,
boolean notEqualTo) {
res =
super.strengthenAnnotationOfEqualTo(
res, firstNode, secondNode, firstValue, secondValue, notEqualTo);
if (firstNode instanceof NullLiteralNode) {
NullnessStore thenStore = res.getThenStore();
NullnessStore elseStore = res.getElseStore();
List<Node> secondParts = splitAssignments(secondNode);
for (Node secondPart : secondParts) {
JavaExpression secondInternal = JavaExpression.fromNode(secondPart);
if (CFAbstractStore.canInsertJavaExpression(secondInternal)) {
thenStore = thenStore == null ? res.getThenStore() : thenStore;
elseStore = elseStore == null ? res.getElseStore() : elseStore;
if (notEqualTo) {
thenStore.insertValue(secondInternal, NONNULL);
} else {
elseStore.insertValue(secondInternal, NONNULL);
}
}
}
Set<AnnotationMirror> secondAnnos =
secondValue != null
? secondValue.getAnnotations()
: AnnotationUtils.createAnnotationSet();
if (nullnessTypeFactory.containsSameByClass(secondAnnos, PolyNull.class)) {
thenStore = thenStore == null ? res.getThenStore() : thenStore;
elseStore = elseStore == null ? res.getElseStore() : elseStore;
// TODO: methodTree is null for lambdas. Handle that case. See Issue3850.java.
MethodTree methodTree = analysis.getContainingMethod(secondNode.getTree());
ExecutableElement methodElem =
methodTree == null ? null : TreeUtils.elementFromDeclaration(methodTree);
if (notEqualTo) {
elseStore.setPolyNullNull(true);
if (methodElem != null && polyNullIsNonNull(methodElem, thenStore)) {
thenStore.setPolyNullNonNull(true);
}
} else {
thenStore.setPolyNullNull(true);
if (methodElem != null && polyNullIsNonNull(methodElem, elseStore)) {
elseStore.setPolyNullNonNull(true);
}
}
}
if (thenStore != null) {
return new ConditionalTransferResult<>(res.getResultValue(), thenStore, elseStore);
}
}
return res;
}
/**
* Returns true if every formal parameter that is declared as @PolyNull is currently known to be
* non-null.
*
* @param method a method
* @param s a store
* @return true if every formal parameter declared as @PolyNull is non-null
*/
private boolean polyNullIsNonNull(ExecutableElement method, NullnessStore s) {
// No need to check the receiver, which is always non-null.
for (VariableElement var : method.getParameters()) {
AnnotatedTypeMirror varType = atypeFactory.fromElement(var);
if (containsPolyNullNotAtTopLevel(varType)) {
return false;
}
if (varType.hasAnnotation(POLYNULL)) {
NullnessValue v = s.getValue(new LocalVariable(var));
if (!AnnotationUtils.containsSameByName(v.getAnnotations(), NONNULL)) {
return false;
}
}
}
return true;
}
/**
* A scanner that returns true if there is an occurrence of @PolyNull that is not at the top
* level.
*/
// Not static so it can access field POLYNULL.
private class ContainsPolyNullNotAtTopLevelScanner
extends SimpleAnnotatedTypeScanner<Boolean, Void> {
/**
* True if the top-level type has not yet been processed (by the first call to defaultAction).
*/
private boolean isTopLevel = true;
/** Create a ContainsPolyNullNotAtTopLevelScanner. */
ContainsPolyNullNotAtTopLevelScanner() {
super(Boolean::logicalOr, false);
}
@Override
protected Boolean defaultAction(AnnotatedTypeMirror type, Void p) {
if (isTopLevel) {
isTopLevel = false;
return false;
} else {
return type.hasAnnotation(POLYNULL);
}
}
}
/**
* Returns true if there is an occurrence of @PolyNull that is not at the top level.
*
* @param t a type
* @return true if there is an occurrence of @PolyNull that is not at the top level
*/
private boolean containsPolyNullNotAtTopLevel(AnnotatedTypeMirror t) {
return new ContainsPolyNullNotAtTopLevelScanner().visit(t);
}
@Override
public TransferResult<NullnessValue, NullnessStore> visitArrayAccess(
ArrayAccessNode n, TransferInput<NullnessValue, NullnessStore> p) {
TransferResult<NullnessValue, NullnessStore> result = super.visitArrayAccess(n, p);
makeNonNull(result, n.getArray());
return result;
}
@Override
public TransferResult<NullnessValue, NullnessStore> visitInstanceOf(
InstanceOfNode n, TransferInput<NullnessValue, NullnessStore> p) {
TransferResult<NullnessValue, NullnessStore> result = super.visitInstanceOf(n, p);
NullnessStore thenStore = result.getThenStore();
NullnessStore elseStore = result.getElseStore();
makeNonNull(thenStore, n.getOperand());
return new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore);
}
@Override
public TransferResult<NullnessValue, NullnessStore> visitMethodAccess(
MethodAccessNode n, TransferInput<NullnessValue, NullnessStore> p) {
TransferResult<NullnessValue, NullnessStore> result = super.visitMethodAccess(n, p);
makeNonNull(result, n.getReceiver());
return result;
}
@Override
public TransferResult<NullnessValue, NullnessStore> visitFieldAccess(
FieldAccessNode n, TransferInput<NullnessValue, NullnessStore> p) {
TransferResult<NullnessValue, NullnessStore> result = super.visitFieldAccess(n, p);
makeNonNull(result, n.getReceiver());
return result;
}
@Override
public TransferResult<NullnessValue, NullnessStore> visitThrow(
ThrowNode n, TransferInput<NullnessValue, NullnessStore> p) {
TransferResult<NullnessValue, NullnessStore> result = super.visitThrow(n, p);
makeNonNull(result, n.getExpression());
return result;
}
/*
* Provided that m is of a type that implements interface java.util.Map:
* <ul>
* <li>Given a call m.get(k), if k is @KeyFor("m") and m's value type is @NonNull,
* then the result is @NonNull in the thenStore and elseStore of the transfer result.
* </ul>
*/
@Override
public TransferResult<NullnessValue, NullnessStore> visitMethodInvocation(
MethodInvocationNode n, TransferInput<NullnessValue, NullnessStore> in) {
TransferResult<NullnessValue, NullnessStore> result = super.visitMethodInvocation(n, in);
// Make receiver non-null.
Node receiver = n.getTarget().getReceiver();
makeNonNull(result, receiver);
// For all formal parameters with a non-null annotation, make the actual argument non-null.
// The point of this is to prevent cascaded errors -- the Nullness Checker will issue a
// warning for the method invocation, but not for subsequent uses of the argument. See test
// case FlowNullness.java.
MethodInvocationTree tree = n.getTree();
ExecutableElement method = TreeUtils.elementFromUse(tree);
AnnotatedExecutableType methodType = nullnessTypeFactory.getAnnotatedType(method);
List<AnnotatedTypeMirror> methodParams = methodType.getParameterTypes();
List<? extends ExpressionTree> methodArgs = tree.getArguments();
for (int i = 0; i < methodParams.size() && i < methodArgs.size(); ++i) {
if (methodParams.get(i).hasAnnotation(NONNULL)) {
makeNonNull(result, n.getArgument(i));
}
}
// Refine result to @NonNull if n is an invocation of Map.get, the argument is a key for
// the map, and the map's value type is not @Nullable.
if (keyForTypeFactory != null && keyForTypeFactory.isMapGet(n)) {
String mapName = JavaExpression.fromNode(receiver).toString();
AnnotatedTypeMirror receiverType = nullnessTypeFactory.getReceiverType(n.getTree());
if (keyForTypeFactory.isKeyForMap(mapName, methodArgs.get(0))
&& !hasNullableValueType(receiverType)) {
makeNonNull(result, n);
refineToNonNull(result);
}
}
return result;
}
/**
* Returns true if mapType's value type (the V type argument to Map) is @Nullable.
*
* @param mapOrSubtype the Map type, or a subtype
* @return true if mapType's value type is @Nullable
*/
private boolean hasNullableValueType(AnnotatedTypeMirror mapOrSubtype) {
AnnotatedDeclaredType mapType =
AnnotatedTypes.asSuper(nullnessTypeFactory, mapOrSubtype, MAP_TYPE);
int numTypeArguments = mapType.getTypeArguments().size();
if (numTypeArguments != 2) {
throw new BugInCF("Wrong number %d of type arguments: %s", numTypeArguments, mapType);
}
AnnotatedTypeMirror valueType = mapType.getTypeArguments().get(1);
return valueType.hasAnnotation(NULLABLE);
}
@Override
public TransferResult<NullnessValue, NullnessStore> visitReturn(
ReturnNode n, TransferInput<NullnessValue, NullnessStore> in) {
TransferResult<NullnessValue, NullnessStore> result = super.visitReturn(n, in);
if (result.getResultValue() == null) {
// Make sure there is a value for return statements, to record (at this return
// statement) the values of isPolyNullNotNull and isPolyNullNull.
return recreateTransferResult(createDummyValue(), result);
} else {
return result;
}
}
/** Creates a dummy abstract value (whose type is not supposed to be looked at). */
private NullnessValue createDummyValue() {
TypeMirror dummy = analysis.getEnv().getTypeUtils().getPrimitiveType(TypeKind.BOOLEAN);
Set<AnnotationMirror> annos = AnnotationUtils.createAnnotationSet();
annos.addAll(nullnessTypeFactory.getQualifierHierarchy().getBottomAnnotations());
return new NullnessValue(analysis, annos, dummy);
}
}