All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.checkerframework.checker.nullness.NullnessAnnotatedTypeFactory Maven / Gradle / Ivy

package org.checkerframework.checker.nullness;

import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.CompoundAssignmentTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.ReturnTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.UnaryTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.VariableElement;
import org.checkerframework.checker.initialization.InitializationAnnotatedTypeFactory;
import org.checkerframework.checker.initialization.qual.FBCBottom;
import org.checkerframework.checker.initialization.qual.Initialized;
import org.checkerframework.checker.initialization.qual.UnderInitialization;
import org.checkerframework.checker.initialization.qual.UnknownInitialization;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.NonRaw;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.nullness.qual.PolyNull;
import org.checkerframework.checker.nullness.qual.Raw;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.framework.flow.CFAbstractAnalysis;
import org.checkerframework.framework.qual.PolyAll;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeFormatter;
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.AnnotatedTypeMirror.AnnotatedWildcardType;
import org.checkerframework.framework.type.QualifierHierarchy;
import org.checkerframework.framework.type.treeannotator.ImplicitsTreeAnnotator;
import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
import org.checkerframework.framework.type.treeannotator.PropagationTreeAnnotator;
import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
import org.checkerframework.framework.type.typeannotator.ImplicitsTypeAnnotator;
import org.checkerframework.framework.type.typeannotator.ListTypeAnnotator;
import org.checkerframework.framework.type.typeannotator.PropagationTypeAnnotator;
import org.checkerframework.framework.type.typeannotator.TypeAnnotator;
import org.checkerframework.framework.util.AnnotatedTypes;
import org.checkerframework.framework.util.MultiGraphQualifierHierarchy.MultiGraphFactory;
import org.checkerframework.javacutil.AnnotationBuilder;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.Pair;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypesUtils;

/** The annotated type factory for the nullness type-system. */
public class NullnessAnnotatedTypeFactory
        extends InitializationAnnotatedTypeFactory<
                NullnessValue, NullnessStore, NullnessTransfer, NullnessAnalysis> {

    /** Annotation constants */
    protected final AnnotationMirror NONNULL, NULLABLE, POLYNULL, MONOTONIC_NONNULL;

    protected final SystemGetPropertyHandler systemGetPropertyHandler;
    protected final CollectionToArrayHeuristics collectionToArrayHeuristics;

    /** Cache for the nullness annotations */
    protected final Set> nullnessAnnos;

    @SuppressWarnings("deprecation") // aliasing to deprecated annotation
    public NullnessAnnotatedTypeFactory(BaseTypeChecker checker, boolean useFbc) {
        super(checker, useFbc);

        NONNULL = AnnotationBuilder.fromClass(elements, NonNull.class);
        NULLABLE = AnnotationBuilder.fromClass(elements, Nullable.class);
        POLYNULL = AnnotationBuilder.fromClass(elements, PolyNull.class);
        MONOTONIC_NONNULL = AnnotationBuilder.fromClass(elements, MonotonicNonNull.class);

        Set> tempNullnessAnnos = new LinkedHashSet<>();
        tempNullnessAnnos.add(NonNull.class);
        tempNullnessAnnos.add(MonotonicNonNull.class);
        tempNullnessAnnos.add(Nullable.class);
        tempNullnessAnnos.add(PolyNull.class);
        tempNullnessAnnos.add(PolyAll.class);
        nullnessAnnos = Collections.unmodifiableSet(tempNullnessAnnos);

        // If you update the following, also update ../../../../../docs/manual/nullness-checker.tex
        // Aliases for @Nonnull:
        addAliasedAnnotation(android.annotation.NonNull.class, NONNULL);
        addAliasedAnnotation(android.support.annotation.NonNull.class, NONNULL);
        addAliasedAnnotation(com.sun.istack.internal.NotNull.class, NONNULL);
        addAliasedAnnotation(edu.umd.cs.findbugs.annotations.NonNull.class, NONNULL);
        addAliasedAnnotation(javax.annotation.Nonnull.class, NONNULL);
        addAliasedAnnotation(javax.validation.constraints.NotNull.class, NONNULL);
        addAliasedAnnotation(lombok.NonNull.class, NONNULL);
        addAliasedAnnotation(org.eclipse.jdt.annotation.NonNull.class, NONNULL);
        addAliasedAnnotation(org.eclipse.jgit.annotations.NonNull.class, NONNULL);
        addAliasedAnnotation(org.jetbrains.annotations.NotNull.class, NONNULL);
        addAliasedAnnotation(org.jmlspecs.annotation.NonNull.class, NONNULL);
        addAliasedAnnotation(org.netbeans.api.annotations.common.NonNull.class, NONNULL);
        addAliasedAnnotation(org.springframework.lang.NonNull.class, NONNULL);
        // Aliases for @Nullable:
        addAliasedAnnotation(android.annotation.Nullable.class, NULLABLE);
        addAliasedAnnotation(android.support.annotation.Nullable.class, NULLABLE);
        addAliasedAnnotation(com.sun.istack.internal.Nullable.class, NULLABLE);
        addAliasedAnnotation(edu.umd.cs.findbugs.annotations.CheckForNull.class, NULLABLE);
        addAliasedAnnotation(edu.umd.cs.findbugs.annotations.Nullable.class, NULLABLE);
        addAliasedAnnotation(edu.umd.cs.findbugs.annotations.PossiblyNull.class, NULLABLE);
        addAliasedAnnotation(edu.umd.cs.findbugs.annotations.UnknownNullness.class, NULLABLE);
        addAliasedAnnotation(javax.annotation.CheckForNull.class, NULLABLE);
        addAliasedAnnotation(javax.annotation.Nullable.class, NULLABLE);
        addAliasedAnnotation(org.eclipse.jdt.annotation.Nullable.class, NULLABLE);
        addAliasedAnnotation(org.eclipse.jgit.annotations.Nullable.class, NULLABLE);
        addAliasedAnnotation(org.jetbrains.annotations.Nullable.class, NULLABLE);
        addAliasedAnnotation(org.jmlspecs.annotation.Nullable.class, NULLABLE);
        addAliasedAnnotation(org.netbeans.api.annotations.common.CheckForNull.class, NULLABLE);
        addAliasedAnnotation(org.netbeans.api.annotations.common.NullAllowed.class, NULLABLE);
        addAliasedAnnotation(org.netbeans.api.annotations.common.NullUnknown.class, NULLABLE);
        addAliasedAnnotation(org.springframework.lang.Nullable.class, NULLABLE);

        // Add compatibility annotations:
        addAliasedAnnotation(
                org.checkerframework.checker.nullness.compatqual.NullableDecl.class, NULLABLE);
        addAliasedAnnotation(
                org.checkerframework.checker.nullness.compatqual.PolyNullDecl.class, POLYNULL);
        addAliasedAnnotation(
                org.checkerframework.checker.nullness.compatqual.NonNullDecl.class, NONNULL);
        addAliasedAnnotation(
                org.checkerframework.checker.nullness.compatqual.MonotonicNonNullDecl.class,
                MONOTONIC_NONNULL);
        addAliasedAnnotation(
                org.checkerframework.checker.nullness.compatqual.NullableType.class, NULLABLE);
        addAliasedAnnotation(
                org.checkerframework.checker.nullness.compatqual.PolyNullType.class, POLYNULL);
        addAliasedAnnotation(
                org.checkerframework.checker.nullness.compatqual.NonNullType.class, NONNULL);
        addAliasedAnnotation(
                org.checkerframework.checker.nullness.compatqual.MonotonicNonNullType.class,
                MONOTONIC_NONNULL);

        systemGetPropertyHandler = new SystemGetPropertyHandler(processingEnv, this);

        postInit();

        // do this last, as it might use the factory again.
        this.collectionToArrayHeuristics = new CollectionToArrayHeuristics(processingEnv, this);
    }

    @Override
    protected Set> createSupportedTypeQualifiers() {
        // NullnessATF is used by both NullnessChecker and NullnessRawnessChecker, load the correct
        // set of qualifiers here
        AbstractNullnessChecker ckr = (AbstractNullnessChecker) checker;
        // if useFbc is true, then it is the NullnessChecker
        if (ckr.useFbc) {
            return new LinkedHashSet>(
                    Arrays.asList(
                            Nullable.class,
                            MonotonicNonNull.class,
                            NonNull.class,
                            UnderInitialization.class,
                            Initialized.class,
                            UnknownInitialization.class,
                            FBCBottom.class,
                            PolyNull.class,
                            PolyAll.class));
        }
        // otherwise, it is the NullnessRawnessChecker
        else {
            return new LinkedHashSet>(
                    Arrays.asList(
                            Nullable.class,
                            MonotonicNonNull.class,
                            NonNull.class,
                            NonRaw.class,
                            Raw.class,
                            // PolyRaw.class, //TODO: support PolyRaw in the future
                            PolyNull.class,
                            PolyAll.class));
        }
    }

    /**
     * For types of left-hand side of an assignment, this method replaces {@link PolyNull} or {@link
     * PolyAll} with {@link Nullable} if the org.checkerframework.dataflow analysis has determined
     * that this is allowed soundly. For example:
     *
     * 
 @PolyNull String foo(@PolyNull String param) {
     *    if (param == null) {
     *        //  @PolyNull is really @Nullable, so change
     *        // the type of param to @Nullable.
     *        param = null;
     *    }
     *    return param;
     * }
     * 
* * @param lhsType type to replace whose polymorphic qualifier will be replaced * @param context tree used to get dataflow value */ protected void replacePolyQualifier(AnnotatedTypeMirror lhsType, Tree context) { if (lhsType.hasAnnotation(PolyNull.class) || lhsType.hasAnnotation(PolyAll.class)) { NullnessValue inferred = getInferredValueFor(context); if (inferred != null && inferred.isPolyNullNull) { lhsType.replaceAnnotation(NULLABLE); } } } @Override public List getUninitializedInvariantFields( NullnessStore store, TreePath path, boolean isStatic, List receiverAnnotations) { List candidates = super.getUninitializedInvariantFields(store, path, isStatic, receiverAnnotations); List result = new ArrayList<>(); for (VariableTree c : candidates) { AnnotatedTypeMirror type = getAnnotatedType(c); boolean isPrimitive = TypesUtils.isPrimitive(type.getUnderlyingType()); if (!isPrimitive) { // primitives do not need to be initialized result.add(c); } } return result; } @Override protected NullnessAnalysis createFlowAnalysis( List> fieldValues) { return new NullnessAnalysis(checker, this, fieldValues); } @Override public NullnessTransfer createFlowTransferFunction( CFAbstractAnalysis analysis) { return new NullnessTransfer((NullnessAnalysis) analysis); } /** @return an AnnotatedTypeFormatter that does not print the qualifiers on null literals */ @Override protected AnnotatedTypeFormatter createAnnotatedTypeFormatter() { return new NullnessAnnotatedTypeFormatter( checker.hasOption("printVerboseGenerics"), checker.hasOption("printAllQualifiers")); } @Override public Pair> methodFromUse( MethodInvocationTree tree) { Pair> mfuPair = super.methodFromUse(tree); AnnotatedExecutableType method = mfuPair.first; systemGetPropertyHandler.handle(tree, method); collectionToArrayHeuristics.handle(tree, method); return mfuPair; } @Override public void adaptGetClassReturnTypeToReceiver( final AnnotatedExecutableType getClassType, final AnnotatedTypeMirror receiverType) { super.adaptGetClassReturnTypeToReceiver(getClassType, receiverType); // Make the wildcard always @NonNull, regardless of the declared type. final AnnotatedDeclaredType returnAdt = (AnnotatedDeclaredType) getClassType.getReturnType(); final List typeArgs = returnAdt.getTypeArguments(); final AnnotatedWildcardType classWildcardArg = (AnnotatedWildcardType) typeArgs.get(0); classWildcardArg.getExtendsBoundField().replaceAnnotation(NONNULL); } @Override public AnnotatedTypeMirror getMethodReturnType(MethodTree m, ReturnTree r) { AnnotatedTypeMirror result = super.getMethodReturnType(m, r); replacePolyQualifier(result, r); return result; } @Override protected TypeAnnotator createTypeAnnotator() { ImplicitsTypeAnnotator implicitsTypeAnnotator = new ImplicitsTypeAnnotator(this); implicitsTypeAnnotator.addTypeClass(AnnotatedTypeMirror.AnnotatedNoType.class, NONNULL); implicitsTypeAnnotator.addTypeClass( AnnotatedTypeMirror.AnnotatedPrimitiveType.class, NONNULL); return new ListTypeAnnotator( new PropagationTypeAnnotator(this), implicitsTypeAnnotator, new NullnessTypeAnnotator(this), new CommitmentTypeAnnotator(this)); } @Override protected TreeAnnotator createTreeAnnotator() { // Don't call super.createTreeAnnotator because the default tree annotators are incorrect // for the Nullness Checker. ImplicitsTreeAnnotator implicitsTreeAnnotator = new ImplicitsTreeAnnotator(this); implicitsTreeAnnotator.addTreeKind(Tree.Kind.NEW_CLASS, NONNULL); implicitsTreeAnnotator.addTreeKind(Tree.Kind.NEW_ARRAY, NONNULL); return new ListTreeAnnotator( // DebugListTreeAnnotator(new Tree.Kind[] {Tree.Kind.CONDITIONAL_EXPRESSION}, new NullnessPropagationAnnotator(this), implicitsTreeAnnotator, new NullnessTreeAnnotator(this), new CommitmentTreeAnnotator(this)); } /** * If the element is {@link NonNull} when used in a static member access, modifies the element's * type (by adding {@link NonNull}). * * @param elt the element being accessed * @param type the type of the element {@code elt} */ private void annotateIfStatic(Element elt, AnnotatedTypeMirror type) { if (elt == null) { return; } if (elt.getKind().isClass() || elt.getKind().isInterface() // Workaround for System.{out,in,err} issue: assume all static // fields in java.lang.System are nonnull. || isSystemField(elt)) { type.replaceAnnotation(NONNULL); } } private static boolean isSystemField(Element elt) { if (!elt.getKind().isField()) { return false; } if (!ElementUtils.isStatic(elt) || !ElementUtils.isFinal(elt)) { return false; } VariableElement var = (VariableElement) elt; // Heuristic: if we have a static final field in a system package, // treat it as NonNull (many like Boolean.TYPE and System.out // have constant value null but are set by the VM). boolean inJavaPackage = ElementUtils.getQualifiedClassName(var).toString().startsWith("java."); return (var.getConstantValue() != null || var.getSimpleName().contentEquals("class") || inJavaPackage); } /** * Nullness doesn't call propagation on binary and unary because the result is * always @Initialized (the default qualifier). * *

Would this be valid to move into CommitmentTreeAnnotator. */ protected static class NullnessPropagationAnnotator extends PropagationTreeAnnotator { public NullnessPropagationAnnotator(AnnotatedTypeFactory atypeFactory) { super(atypeFactory); } @Override public Void visitBinary(BinaryTree node, AnnotatedTypeMirror type) { return null; } @Override public Void visitUnary(UnaryTree node, AnnotatedTypeMirror type) { return null; } } protected class NullnessTreeAnnotator extends TreeAnnotator /*extends InitializationAnnotatedTypeFactory.CommitmentTreeAnnotator*/ { public NullnessTreeAnnotator(NullnessAnnotatedTypeFactory atypeFactory) { super(atypeFactory); } @Override public Void visitMemberSelect(MemberSelectTree node, AnnotatedTypeMirror type) { Element elt = TreeUtils.elementFromUse(node); assert elt != null; // case 8: class in static member access annotateIfStatic(elt, type); return null; } @Override public Void visitVariable(VariableTree node, AnnotatedTypeMirror type) { Element elt = TreeUtils.elementFromTree(node); if (elt.getKind() == ElementKind.EXCEPTION_PARAMETER) { if (!type.isAnnotatedInHierarchy(NONNULL)) { // case 9. exception parameter type.addAnnotation(NONNULL); } } return null; } @Override public Void visitIdentifier(IdentifierTree node, AnnotatedTypeMirror type) { Element elt = TreeUtils.elementFromUse(node); assert elt != null; // case 8. static method access annotateIfStatic(elt, type); if (elt.getKind() == ElementKind.EXCEPTION_PARAMETER) { // TODO: It's surprising that we have to do this in // both visitVariable and visitIdentifier. This should // already be handled by applying the defaults anyway. // case 9. exception parameter type.replaceAnnotation(NONNULL); } return null; } // The result of a binary operation is always non-null. @Override public Void visitBinary(BinaryTree node, AnnotatedTypeMirror type) { type.replaceAnnotation(NONNULL); return null; } // The result of a compound operation is always non-null. @Override public Void visitCompoundAssignment(CompoundAssignmentTree node, AnnotatedTypeMirror type) { type.replaceAnnotation(NONNULL); // Commitment will run after for initialization defaults return null; } // The result of a unary operation is always non-null. @Override public Void visitUnary(UnaryTree node, AnnotatedTypeMirror type) { type.replaceAnnotation(NONNULL); return null; } // The result of newly allocated structures is always non-null. @Override public Void visitNewClass(NewClassTree node, AnnotatedTypeMirror type) { type.replaceAnnotation(NONNULL); return null; } } protected class NullnessTypeAnnotator extends InitializationAnnotatedTypeFactory< NullnessValue, NullnessStore, NullnessTransfer, NullnessAnalysis> .CommitmentTypeAnnotator { public NullnessTypeAnnotator(InitializationAnnotatedTypeFactory atypeFactory) { super(atypeFactory); } } /** @return the list of annotations of the non-null type system */ public Set> getNullnessAnnotations() { return nullnessAnnos; } @Override public Set> getInvalidConstructorReturnTypeAnnotations() { Set> l = new HashSet<>(super.getInvalidConstructorReturnTypeAnnotations()); l.addAll(getNullnessAnnotations()); return l; } @Override public AnnotationMirror getFieldInvariantAnnotation() { return NONNULL; } /** * {@inheritDoc} * *

In other words, is the lower bound @NonNull? * * @param type of field that might have invariant annotation * @return whether or not type has the invariant annotation */ @Override protected boolean hasFieldInvariantAnnotation(AnnotatedTypeMirror type) { AnnotationMirror invariant = getFieldInvariantAnnotation(); Set lowerBounds = AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, type); return AnnotationUtils.containsSame(lowerBounds, invariant); } @Override public QualifierHierarchy createQualifierHierarchy(MultiGraphFactory factory) { return new NullnessQualifierHierarchy(factory, (Object[]) null); } protected class NullnessQualifierHierarchy extends InitializationQualifierHierarchy { public NullnessQualifierHierarchy(MultiGraphFactory f, Object[] arg) { super(f, arg); } @Override public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) { if (isInitializationAnnotation(subAnno) || isInitializationAnnotation(superAnno)) { return this.isSubtypeInitialization(subAnno, superAnno); } return super.isSubtype(subAnno, superAnno); } @Override public AnnotationMirror leastUpperBound(AnnotationMirror a1, AnnotationMirror a2) { if (isInitializationAnnotation(a1) || isInitializationAnnotation(a2)) { return this.leastUpperBoundInitialization(a1, a2); } return super.leastUpperBound(a1, a2); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy