package org.checkerframework.checker.nullness; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.MethodInvocationTree; import java.util.List; import java.util.Set; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; 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.FlowExpressions; import org.checkerframework.dataflow.analysis.FlowExpressions.Receiver; import org.checkerframework.dataflow.analysis.RegularTransferResult; 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.framework.flow.CFAbstractStore; import org.checkerframework.framework.qual.PolyAll; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.TreeUtils; /** * 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. * <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 Nullable}. * </ol> * * @author Stefan Heule */ public class NullnessTransfer extends InitializationTransfer<NullnessValue, NullnessTransfer, NullnessStore> { /** Type-specific version of super.analysis. */ protected final NullnessAnalysis analysis; /** Annotations of the non-null type system. */ protected final AnnotationMirror NONNULL, NULLABLE; protected final KeyForAnnotatedTypeFactory keyForTypeFactory; public NullnessTransfer(NullnessAnalysis analysis) { super(analysis); this.analysis = analysis; this.keyForTypeFactory = ((BaseTypeChecker) analysis.getTypeFactory().getContext().getChecker()) .getTypeFactoryOfSubchecker(KeyForSubchecker.class); NONNULL = AnnotationUtils.fromClass( analysis.getTypeFactory().getElementUtils(), NonNull.class); NULLABLE = AnnotationUtils.fromClass( analysis.getTypeFactory().getElementUtils(), Nullable.class); } /** * Sets a given {@link Node} to non-null in the given {@code store}. Calls to this method * implement case 2. */ protected void makeNonNull(NullnessStore store, Node node) { Receiver internalRepr = FlowExpressions.internalReprOf(analysis.getTypeFactory(), 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); } } @Override protected NullnessValue finishValue(NullnessValue value, NullnessStore store) { value = super.finishValue(value, store); if (value != null) { value.isPolyNullNull = store.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) { Receiver secondInternal = FlowExpressions.internalReprOf(analysis.getTypeFactory(), secondPart); if (CFAbstractStore.canInsertReceiver(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 (AnnotationUtils.containsSameByClass(secondAnnos, PolyNull.class) || AnnotationUtils.containsSameByClass(secondAnnos, PolyAll.class)) { thenStore = thenStore == null ? res.getThenStore() : thenStore; elseStore = elseStore == null ? res.getElseStore() : elseStore; thenStore.setPolyNullNull(true); } if (thenStore != null) { return new ConditionalTransferResult<>(res.getResultValue(), thenStore, elseStore); } } return res; } @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"), ensures that 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. makeNonNull(result, n.getTarget().getReceiver()); // For all formal parameters with a non-null annotation, make the actual // argument non-null. MethodInvocationTree tree = n.getTree(); ExecutableElement method = TreeUtils.elementFromUse(tree); AnnotatedExecutableType methodType = analysis.getTypeFactory().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 and the argument is a key for // the map. if (keyForTypeFactory.isInvocationOfMapMethod(n, "get")) { Node receiver = n.getTarget().getReceiver(); String mapName = FlowExpressions.internalReprOf(analysis.getTypeFactory(), receiver).toString(); if (keyForTypeFactory.isKeyForMap(mapName, methodArgs.get(0))) { makeNonNull(result, n); NullnessValue oldResultValue = result.getResultValue(); NullnessValue refinedResultValue = analysis.createSingleAnnotationValue( NONNULL, oldResultValue.getUnderlyingType()); NullnessValue newResultValue = refinedResultValue.mostSpecific(oldResultValue, null); result.setResultValue(newResultValue); } } return result; } @Override public TransferResult<NullnessValue, NullnessStore> visitReturn( ReturnNode n, TransferInput<NullnessValue, NullnessStore> in) { // HACK: make sure we have a value for return statements, because we // need to record whether (at this return statement) isPolyNullNull is // set or not. NullnessValue value = createDummyValue(); if (in.containsTwoStores()) { NullnessStore thenStore = in.getThenStore(); NullnessStore elseStore = in.getElseStore(); return new ConditionalTransferResult<>( finishValue(value, thenStore, elseStore), thenStore, elseStore); } else { NullnessStore info = in.getRegularStore(); return new RegularTransferResult<>(finishValue(value, info), info); } } /** 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(analysis.getTypeFactory().getQualifierHierarchy().getBottomAnnotations()); return new NullnessValue(analysis, annos, dummy); } }