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

checker.src.org.checkerframework.checker.lock.LockAnnotatedTypeFactory Maven / Gradle / Ivy

Go to download

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.

There is a newer version: 3.42.0
Show newest version
package org.checkerframework.checker.lock;

/*>>>
import org.checkerframework.checker.interning.qual.*;
*/

import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.framework.flow.CFAbstractAnalysis;
import org.checkerframework.framework.flow.CFValue;
import org.checkerframework.checker.lock.qual.GuardSatisfied;
import org.checkerframework.checker.lock.qual.GuardedBy;
import org.checkerframework.checker.lock.qual.GuardedByBottom;
import org.checkerframework.checker.lock.qual.GuardedByUnknown;
import org.checkerframework.checker.lock.qual.LockHeld;
import org.checkerframework.checker.lock.qual.LockPossiblyHeld;
import org.checkerframework.checker.lock.qual.LockingFree;
import org.checkerframework.checker.lock.qual.MayReleaseLocks;
import org.checkerframework.checker.lock.qual.ReleasesNoLocks;
import org.checkerframework.dataflow.qual.Pure;
import org.checkerframework.dataflow.qual.SideEffectFree;
import org.checkerframework.framework.type.*;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
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.util.AnnotatedTypes;
import org.checkerframework.framework.util.AnnotationBuilder;
import org.checkerframework.framework.util.MultiGraphQualifierHierarchy;
import org.checkerframework.framework.util.MultiGraphQualifierHierarchy.MultiGraphFactory;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.InternalUtils;
import org.checkerframework.javacutil.Pair;

import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.VariableTree;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
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.ExecutableElement;
import javax.lang.model.element.VariableElement;

/**
 * LockAnnotatedTypeFactory builds types with LockHeld and LockPossiblyHeld annotations.
 * LockHeld identifies that an object is being used as a lock and is being held when a
 * given tree is executed. LockPossiblyHeld is the default type qualifier for this
 * hierarchy and applies to all fields, local variables and parameters - hence it does
 * not convey any information other than that it is not LockHeld.
 *
 * However, there are a number of other annotations used in conjunction with these annotations
 * to enforce proper locking.
 * @checker_framework.manual #lock-checker Lock Checker
 */
public class LockAnnotatedTypeFactory
    extends GenericAnnotatedTypeFactory {

    /** Annotation constants */
    protected final AnnotationMirror LOCKHELD, LOCKPOSSIBLYHELD,
        SIDEEFFECTFREE, GUARDEDBYUNKNOWN, GUARDEDBY,
        GUARDEDBYBOTTOM, GUARDSATISFIED;

    public LockAnnotatedTypeFactory(BaseTypeChecker checker) {
        super(checker, true);

        LOCKHELD = AnnotationUtils.fromClass(elements, LockHeld.class);
        LOCKPOSSIBLYHELD = AnnotationUtils.fromClass(elements, LockPossiblyHeld.class);
        SIDEEFFECTFREE = AnnotationUtils.fromClass(elements, SideEffectFree.class);
        GUARDEDBYUNKNOWN = AnnotationUtils.fromClass(elements, GuardedByUnknown.class);
        GUARDEDBY = AnnotationUtils.fromClass(elements, GuardedBy.class);
        GUARDEDBYBOTTOM = AnnotationUtils.fromClass(elements, GuardedByBottom.class);
        GUARDSATISFIED = AnnotationUtils.fromClass(elements, GuardSatisfied.class);

        // This alias is only true for the Lock Checker. All other checkers must
        // ignore the @LockingFree annotation.
        addAliasedDeclAnnotation(LockingFree.class,
                SideEffectFree.class,
                SIDEEFFECTFREE);

        // This alias is only true for the Lock Checker. All other checkers must
        // ignore the @ReleasesNoLocks annotation.  Note that ReleasesNoLocks is
        // not truly side-effect-free even as far as the Lock Checker is concerned,
        // so there is additional handling of this annotation in the Lock Checker.
        addAliasedDeclAnnotation(ReleasesNoLocks.class,
                SideEffectFree.class,
                SIDEEFFECTFREE);

        postInit();
    }

    @Override
    protected Set> createSupportedTypeQualifiers() {
        return Collections.unmodifiableSet(
                new LinkedHashSet>(
                        Arrays.asList(LockHeld.class, LockPossiblyHeld.class,
                                GuardedBy.class, GuardedByUnknown.class,
                                GuardSatisfied.class, GuardedByBottom.class)));
    }

    @Override
    public QualifierHierarchy createQualifierHierarchy(MultiGraphFactory factory) {
        return new LockQualifierHierarchy(factory);
    }

    @Override
    protected LockAnalysis createFlowAnalysis(List> fieldValues) {
        return new LockAnalysis(checker, this, fieldValues);
    }

    @Override
    public LockTransfer createFlowTransferFunction(CFAbstractAnalysis analysis) {
        return new LockTransfer((LockAnalysis) analysis,(LockChecker)this.checker);
    }

    class LockQualifierHierarchy extends MultiGraphQualifierHierarchy {

        public LockQualifierHierarchy(MultiGraphFactory f) {
            super(f, LOCKHELD);
        }

        boolean isGuardedBy(AnnotationMirror am) {
            return AnnotationUtils.areSameIgnoringValues(am, GUARDEDBY);
        }

        boolean isGuardSatisfied(AnnotationMirror am) {
            return AnnotationUtils.areSameIgnoringValues(am, GUARDSATISFIED);
        }

        @Override
        public boolean isSubtype(AnnotationMirror rhs, AnnotationMirror lhs) {

            boolean lhsIsGuardedBy = isGuardedBy(lhs);
            boolean rhsIsGuardedBy = isGuardedBy(rhs);

            if (lhsIsGuardedBy && rhsIsGuardedBy) {
                // Two @GuardedBy annotations are considered subtypes of each other if and only if their values match exactly.

                List lhsValues =
                    AnnotationUtils.getElementValueArray(lhs, "value", String.class, true);
                List rhsValues =
                    AnnotationUtils.getElementValueArray(rhs, "value", String.class, true);

                return rhsValues.containsAll(lhsValues) && lhsValues.containsAll(rhsValues);
            }

            boolean lhsIsGuardSatisfied = isGuardSatisfied(lhs);
            boolean rhsIsGuardSatisfied = isGuardSatisfied(rhs);

            if (lhsIsGuardSatisfied && rhsIsGuardSatisfied) {
                // There are cases in which two expressions with identical @GuardSatisfied(...) annotations are not
                // assignable. Those are handled elsewhere.

                // Two expressions with @GuardSatisfied annotations (without an index) are sometimes not assignable.
                // For example, two method actual parameters with @GuardSatisfied annotations are assumed to refer to different guards.

                // This is largely handled in methodFromUse and in LockVisitor.visitMethodInvocation.
                // Related behavior is handled in LockVisitor.visitMethod (issuing an error if a non-constructor method
                // definition has a return type of @GuardSatisfied without an index).

                // Two expressions with @GuardSatisfied() annotations are assignable when comparing a formal receiver
                // to an actual receiver (see LockVisitor.skipReceiverSubtypeCheck) or a formal parameter to an
                // actual parameter (see LockVisitor.commonAssignmentCheck for the details on this rule).

                return AnnotationUtils.areSame(lhs, rhs);
            }

            // Remove values from @GuardedBy annotations for further subtype checking. Remove indices from @GuardSatisfied annotations.

            if (lhsIsGuardedBy) {
                lhs = GUARDEDBY;
            } else if (lhsIsGuardSatisfied) {
                lhs = GUARDSATISFIED;
            }

            if (rhsIsGuardedBy) {
                rhs = GUARDEDBY;
            } else if (rhsIsGuardSatisfied) {
                rhs = GUARDSATISFIED;
            }

            return super.isSubtype(rhs, lhs);
        }

        @Override
        public AnnotationMirror greatestLowerBound(AnnotationMirror a1, AnnotationMirror a2) {
            AnnotationMirror a1top = getTopAnnotation(a1);
            AnnotationMirror a2top = getTopAnnotation(a2);

            if (AnnotationUtils.areSame(a1top, LOCKPOSSIBLYHELD) &&
                AnnotationUtils.areSame(a2top, LOCKPOSSIBLYHELD)) {
                return greatestLowerBoundInLockPossiblyHeldHierarchy(a1, a2);
            } else if (AnnotationUtils.areSame(a1top, GUARDEDBYUNKNOWN) &&
                       AnnotationUtils.areSame(a2top, GUARDEDBYUNKNOWN)) {
                return greatestLowerBoundInGuardedByUnknownHierarchy(a1, a2);
            }

            return null;
        }

        private AnnotationMirror greatestLowerBoundInGuardedByUnknownHierarchy(AnnotationMirror a1, AnnotationMirror a2) {
            if (AnnotationUtils.areSame(a1, GUARDEDBYUNKNOWN)) {
                return a2;
            }

            if (AnnotationUtils.areSame(a2, GUARDEDBYUNKNOWN)) {
                return a1;
            }

            if ((isGuardedBy(a1) && isGuardedBy(a2)) ||
                (isGuardSatisfied(a1) && isGuardSatisfied(a2))) {
                // isSubtype(a1, a2) is symmetrical to isSubtype(a2, a1) since two
                // @GuardedBy annotations are considered subtypes of each other
                // if and only if their values match exactly, and two @GuardSatisfied
                // annotations are considered subtypes of each other if and only if
                // their indices match exactly.

                if (isSubtype(a1, a2)) {
                    return a1;
                }
            }

            return GUARDEDBYBOTTOM;
        }

        private AnnotationMirror greatestLowerBoundInLockPossiblyHeldHierarchy(AnnotationMirror a1, AnnotationMirror a2) {
            if (AnnotationUtils.areSame(a1, LOCKPOSSIBLYHELD)) {
                return a2;
            }

            if (AnnotationUtils.areSame(a2, LOCKPOSSIBLYHELD)) {
                return a1;
            }

            return LOCKHELD;
        }
    }

    // The side effect annotations processed by the Lock Checker.
    enum SideEffectAnnotation {
        MAYRELEASELOCKS("@MayReleaseLocks", MayReleaseLocks.class),
        RELEASESNOLOCKS("@ReleasesNoLocks", ReleasesNoLocks.class),
        LOCKINGFREE("@LockingFree", LockingFree.class),
        SIDEEFFECTFREE("@SideEffectFree", SideEffectFree.class),
        PURE("@Pure", Pure.class);
        final String annotation;
        final  Class annotationClass;

        SideEffectAnnotation(String annotation, Class annotationClass) {
            this.annotation = annotation;
            this.annotationClass = annotationClass;
        }

        public String getNameOfSideEffectAnnotation() {
            return annotation;
        }

        public Class getAnnotationClass() {
            return annotationClass;
        }

        /**
         * Returns true if the receiver side effect annotation is weaker
         * than side effect annotation 'other'.
         */
        boolean isWeakerThan(SideEffectAnnotation other) {
            boolean weaker = false;

            switch (other) {
                case MAYRELEASELOCKS:
                    break;
                case RELEASESNOLOCKS:
                    if (this == SideEffectAnnotation.MAYRELEASELOCKS) {
                        weaker = true;
                    }
                    break;
                case LOCKINGFREE:
                    switch (this) {
                        case MAYRELEASELOCKS:
                        case RELEASESNOLOCKS:
                            weaker = true;
                            break;
                        default:
                    }
                    break;
                case SIDEEFFECTFREE:
                    switch (this) {
                        case MAYRELEASELOCKS:
                        case RELEASESNOLOCKS:
                        case LOCKINGFREE:
                            weaker = true;
                            break;
                        default:
                    }
                    break;
                case PURE:
                    switch (this) {
                        case MAYRELEASELOCKS:
                        case RELEASESNOLOCKS:
                        case LOCKINGFREE:
                        case SIDEEFFECTFREE:
                            weaker = true;
                            break;
                        default:
                    }
                    break;
            }

            return weaker;
        }

        static SideEffectAnnotation weakest = null;
        public static SideEffectAnnotation weakest() {
            if (weakest == null) {
                for (SideEffectAnnotation sea : SideEffectAnnotation.values()) {
                    if (weakest == null) {
                        weakest = sea;
                    }
                    if (sea.isWeakerThan(weakest)) {
                        weakest = sea;
                    }
                }
            }
            return weakest;
        }
    }

    /**
     * Indicates which side effect annotation is present on the given method.
     * If more than one annotation is present, this method issues an error (if issueErrorIfMoreThanOnePresent is true)
     * and returns the annotation providing the weakest guarantee.
     * Only call with issueErrorIfMoreThanOnePresent == true when visiting a method definition.
     * This prevents multiple errors being issued for the same method (as would occur if
     * issueErrorIfMoreThanOnePresent were set to true when visiting method invocations).
     * If no annotation is present, return RELEASESNOLOCKS as the default, and MAYRELEASELOCKS
     * as the default for unchecked code.
     *
     * @param element the method element
     * @param issueErrorIfMoreThanOnePresent whether to issue an error if more than one side effect annotation is present on the method
     */
    // package-private
    SideEffectAnnotation methodSideEffectAnnotation(Element element, boolean issueErrorIfMoreThanOnePresent) {
        if (element != null) {
            List sideEffectAnnotationPresent = new ArrayList<>();
            for (SideEffectAnnotation sea : SideEffectAnnotation.values()) {
                if (getDeclAnnotationNoAliases(element, sea.getAnnotationClass()) != null) {
                    sideEffectAnnotationPresent.add(sea);
                }
            }

            int count = sideEffectAnnotationPresent.size();

            if (count == 0) {
                return defaults.applyUncheckedCodeDefaults(element) ?
                    SideEffectAnnotation.MAYRELEASELOCKS :
                    SideEffectAnnotation.RELEASESNOLOCKS;
            }

            if (count > 1 && issueErrorIfMoreThanOnePresent) {
                // TODO: Turn on after figuring out how this interacts with inherited annotations.
                // checker.report(Result.failure("multiple.sideeffect.annotations"), element);
            }

            SideEffectAnnotation weakest = sideEffectAnnotationPresent.get(0);
            // At least one side effect annotation was found. Return the weakest.
            for (SideEffectAnnotation sea : sideEffectAnnotationPresent) {
                if (sea.isWeakerThan(weakest)) {
                    weakest = sea;
                }
            }
            return weakest;
        }

        // When there is not enough information to determine the correct side effect annotation,
        // return the weakest one.
        return SideEffectAnnotation.weakest();
    }

    /**
     * Returns the index on the GuardSatisfied annotation in the given AnnotatedTypeMirror.
     * Assumes atm is non-null and contains a GuardSatisfied annotation.
     *
     * @param atm AnnotatedTypeMirror containing a GuardSatisfied annotation
     * @return the index on the GuardSatisfied annotation
     */
    // package-private
    int getGuardSatisfiedIndex(AnnotatedTypeMirror atm) {
        return getGuardSatisfiedIndex(atm.getAnnotation(GuardSatisfied.class));
    }

    /**
     * Returns the index on the given GuardSatisfied annotation.
     * Assumes am is non-null and is a GuardSatisfied annotation.
     *
     * @param am AnnotationMirror for a GuardSatisfied annotation
     * @return the index on the GuardSatisfied annotation
     */
    // package-private
    int getGuardSatisfiedIndex(AnnotationMirror am) {
        return AnnotationUtils.
                getElementValue(am, "value", Integer.class, true);
    }

    @Override
    public Pair> methodFromUse(
            ExpressionTree tree, ExecutableElement methodElt,
            AnnotatedTypeMirror receiverType) {
        Pair> mfuPair = super.methodFromUse(tree, methodElt, receiverType);

        if (tree.getKind() != Kind.METHOD_INVOCATION) {
            return mfuPair;
        }

        // If a method's formal return type is annotated with @GuardSatisfied(index),
        // look for the first instance of @GuardSatisfied(index) in the method definition's receiver type or
        // formal parameters, retrieve the corresponding type of the actual parameter / receiver at the call site
        // (e.g. @GuardedBy("someLock") and replace the return type at the call site with this type.

        AnnotatedExecutableType invokedMethod = mfuPair.first;

        if (invokedMethod.getElement().getKind() == ElementKind.CONSTRUCTOR) {
            return mfuPair;
        }

        AnnotatedTypeMirror methodDefinitionReturn = invokedMethod.getReturnType();

        if (methodDefinitionReturn == null || !methodDefinitionReturn.hasAnnotation(GuardSatisfied.class)) {
            return mfuPair;
        }

        int returnGuardSatisfiedIndex = getGuardSatisfiedIndex(methodDefinitionReturn);

        // @GuardSatisfied with no index defaults to index -1. Ignore instances of @GuardSatisfied with no index.
        // If a method is defined with a return type of @GuardSatisfied with no index, an error is reported by LockVisitor.visitMethod.

        if (returnGuardSatisfiedIndex == -1) {
            return mfuPair;
        }

        // Find the receiver or first parameter whose @GS index matches that of the return type.
        // Ensuring that the type annotations on distinct @GS parameters with the same index
        // match at the call site is handled in LockVisitor.visitMethodInvocation

        if (!ElementUtils.isStatic(invokedMethod.getElement()) &&
            replaceAnnotationInGuardedByHierarchyIfGuardSatisfiedIndexMatches(methodDefinitionReturn,
                    invokedMethod.getReceiverType() /* the method definition receiver*/,
                    returnGuardSatisfiedIndex,
                    receiverType.getAnnotationInHierarchy(GUARDEDBYUNKNOWN))) {
            return mfuPair;
        }

        List methodInvocationTreeArguments = ((MethodInvocationTree) tree).getArguments();
        List requiredArgs = AnnotatedTypes.expandVarArgs(this,
                invokedMethod, methodInvocationTreeArguments);

        for (int i = 0; i < requiredArgs.size(); i++) {
            if (replaceAnnotationInGuardedByHierarchyIfGuardSatisfiedIndexMatches(methodDefinitionReturn,
                    requiredArgs.get(i),
                    returnGuardSatisfiedIndex,
                    getAnnotatedType(methodInvocationTreeArguments.get(i)).getEffectiveAnnotationInHierarchy(GUARDEDBYUNKNOWN))) {
                return mfuPair;
            }
        }

        return mfuPair;
    }

    /**
     * If {@code atm} is not null and contains a {@code @GuardSatisfied} annotation, and if the index of this
     * {@code @GuardSatisfied} annotation matches {@code matchingGuardSatisfiedIndex}, then
     * {@code methodReturnAtm} will have its annotation in the {@code @GuardedBy} hierarchy replaced
     * with that in {@code atmWithAnnotationInGuardedByHierarchy}.
     *
     * @param methodReturnAtm the AnnotatedTypeMirror for the return type of a method that will potentially have
     * its annotation in the {@code @GuardedBy} hierarchy replaced.
     * @param atm an AnnotatedTypeMirror that may contain a {@code @GuardSatisfied} annotation. May be null.
     * @param matchingGuardSatisfiedIndex the {code @GuardSatisfied} index that the {@code @GuardSatisfied} annotation
     * in {@code atm} must have in order for the replacement to occur.
     * @param annotationInGuardedByHierarchy if the replacement occurs, the annotation in the {@code @GuardedBy}
     *  hierarchy in this parameter will be used for the replacement.
     * @return true if the replacement occurred, false otherwise
     */
    private boolean replaceAnnotationInGuardedByHierarchyIfGuardSatisfiedIndexMatches(
            AnnotatedTypeMirror methodReturnAtm,
            AnnotatedTypeMirror atm,
            int matchingGuardSatisfiedIndex,
            AnnotationMirror annotationInGuardedByHierarchy) {
        if (atm == null || !atm.hasAnnotation(GuardSatisfied.class) ||
            getGuardSatisfiedIndex(atm) != matchingGuardSatisfiedIndex) {
            return false;
        }

        methodReturnAtm.replaceAnnotation(annotationInGuardedByHierarchy);

        return true;
    }

    @Override
    protected TreeAnnotator createTreeAnnotator() {
        return new ListTreeAnnotator(
               new LockTreeAnnotator(this),
               new PropagationTreeAnnotator(this),
               new ImplicitsTreeAnnotator(this)
        );
    }

    @Override
    public void addComputedTypeAnnotations(Element elt, AnnotatedTypeMirror type) {
        translateJcipAndJavaxAnnotations(elt, type);

        super.addComputedTypeAnnotations(elt, type);
    }

    @Override
    public void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type, boolean useFlow) {
        if (tree.getKind() == Tree.Kind.VARIABLE) {
            translateJcipAndJavaxAnnotations(InternalUtils.symbol((VariableTree) tree), type);
        }

        super.addComputedTypeAnnotations(tree, type, useFlow);
    }

    /**
     * Given a field declaration with a {@code @net.jcip.annotations.GuardedBy} or
     * {@code javax.annotation.concurrent.GuardedBy} annotation and an AnnotatedTypeMirror
     * for that field, inserts the corresponding {@code @org.checkerframework.checker.lock.qual.GuardedBy}
     * type qualifier into that AnnotatedTypeMirror.
     *
     * @param element any Element (this method does nothing if the Element is not for a field declaration)
     * @param atm the AnnotatedTypeMirror for element - the {@code @GuardedBy} type qualifier will be inserted here
     */
    private void translateJcipAndJavaxAnnotations(Element element, AnnotatedTypeMirror atm) {
        if (!element.getKind().isField()) {
            return;
        }

        AnnotationMirror anno = getDeclAnnotation(element, net.jcip.annotations.GuardedBy.class);

        if (anno == null) {
            anno = getDeclAnnotation(element, javax.annotation.concurrent.GuardedBy.class);
        }

        if (anno == null) {
            return;
        }

        List lockExpressions = AnnotationUtils.getElementValueArray(anno, "value", String.class, true);

        if (lockExpressions.isEmpty()) {
            atm.addAnnotation(GUARDEDBY);
        } else {
            atm.addAnnotation(createGuardedByAnnotationMirror(lockExpressions));
        }
    }

    /**
     * @param values a list of lock expressions
     * @return an AnnotationMirror corresponding to @GuardedBy(values)
     */
    private AnnotationMirror createGuardedByAnnotationMirror(List values) {
        AnnotationBuilder builder =
                new AnnotationBuilder(getProcessingEnv(), GuardedBy.class);
        builder.setValue("value", values.toArray());

        // Return the resulting AnnotationMirror
        return builder.build();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy