Search in sources :

Example 1 with SideEffectAnnotation

use of org.checkerframework.checker.lock.LockAnnotatedTypeFactory.SideEffectAnnotation in project checker-framework by typetools.

the class LockVisitor method visitSynchronized.

/**
 * When visiting a synchronized block, issue an error if the expression has a type that
 * implements the java.util.concurrent.locks.Lock interface. This prevents explicit locks from
 * being accidentally used as built-in (monitor) locks. This is important because the Lock
 * Checker does not have a mechanism to separately keep track of the explicit lock and the
 * monitor lock of an expression that implements the Lock interface (i.e. there is a @LockHeld
 * annotation used in dataflow, but there are not distinct @MonitorLockHeld
 * and @ExplicitLockHeld annotations). It is assumed that both kinds of locks will never be held
 * for any expression that implements Lock.
 *
 * <p>Additionally, a synchronized block may not be present in a method that has a @LockingFree
 * guarantee or stronger. An error is issued in this case.
 *
 * @param node the SynchronizedTree for the synchronized block being visited
 */
@Override
public Void visitSynchronized(SynchronizedTree node, Void p) {
    ProcessingEnvironment processingEnvironment = checker.getProcessingEnvironment();
    javax.lang.model.util.Types types = processingEnvironment.getTypeUtils();
    // TODO: make a type declaration annotation for this rather than looking for Lock.class
    // explicitly.
    TypeMirror lockInterfaceTypeMirror = TypesUtils.typeFromClass(Lock.class, types, processingEnvironment.getElementUtils());
    ExpressionTree synchronizedExpression = node.getExpression();
    ensureExpressionIsEffectivelyFinal(synchronizedExpression);
    TypeMirror expressionType = types.erasure(atypeFactory.getAnnotatedType(synchronizedExpression).getUnderlyingType());
    if (types.isSubtype(expressionType, lockInterfaceTypeMirror)) {
        checker.report(Result.failure("explicit.lock.synchronized"), node);
    }
    MethodTree enclosingMethod = TreeUtils.enclosingMethod(atypeFactory.getPath(node));
    ExecutableElement methodElement = null;
    if (enclosingMethod != null) {
        methodElement = TreeUtils.elementFromDeclaration(enclosingMethod);
        SideEffectAnnotation seaOfContainingMethod = atypeFactory.methodSideEffectAnnotation(methodElement, false);
        if (!seaOfContainingMethod.isWeakerThan(SideEffectAnnotation.LOCKINGFREE)) {
            checker.report(Result.failure("synchronized.block.in.lockingfree.method", seaOfContainingMethod), node);
        }
    }
    return super.visitSynchronized(node, p);
}
Also used : AnnotatedTypeMirror(org.checkerframework.framework.type.AnnotatedTypeMirror) TypeMirror(javax.lang.model.type.TypeMirror) MethodTree(com.sun.source.tree.MethodTree) ExecutableElement(javax.lang.model.element.ExecutableElement) ExpressionTree(com.sun.source.tree.ExpressionTree) SideEffectAnnotation(org.checkerframework.checker.lock.LockAnnotatedTypeFactory.SideEffectAnnotation) ProcessingEnvironment(javax.annotation.processing.ProcessingEnvironment)

Example 2 with SideEffectAnnotation

use of org.checkerframework.checker.lock.LockAnnotatedTypeFactory.SideEffectAnnotation in project checker-framework by typetools.

the class LockVisitor method visitMethodInvocation.

/**
 * When visiting a method invocation, issue an error if the side effect annotation on the called
 * method causes the side effect guarantee of the enclosing method to be violated. For example,
 * a method annotated with @ReleasesNoLocks may not call a method annotated
 * with @MayReleaseLocks. Also check that matching @GuardSatisfied(index) on a method's formal
 * receiver/parameters matches those in corresponding locations on the method call site.
 *
 * @param node the MethodInvocationTree of the method call being visited
 */
@Override
public Void visitMethodInvocation(MethodInvocationTree node, Void p) {
    ExecutableElement methodElement = TreeUtils.elementFromUse(node);
    SideEffectAnnotation seaOfInvokedMethod = atypeFactory.methodSideEffectAnnotation(methodElement, false);
    MethodTree enclosingMethod = TreeUtils.enclosingMethod(atypeFactory.getPath(node));
    ExecutableElement enclosingMethodElement = null;
    if (enclosingMethod != null) {
        enclosingMethodElement = TreeUtils.elementFromDeclaration(enclosingMethod);
    }
    if (enclosingMethodElement != null) {
        SideEffectAnnotation seaOfContainingMethod = atypeFactory.methodSideEffectAnnotation(enclosingMethodElement, false);
        if (seaOfInvokedMethod.isWeakerThan(seaOfContainingMethod)) {
            checker.report(Result.failure("method.guarantee.violated", seaOfContainingMethod.getNameOfSideEffectAnnotation(), enclosingMethodElement.toString(), methodElement.toString(), seaOfInvokedMethod.getNameOfSideEffectAnnotation()), node);
        }
    }
    if (methodElement != null) {
        // Handle releasing of explicit locks. Verify that the lock expression is effectively
        // final.
        ExpressionTree recvTree = getReceiverTree(node);
        ensureReceiverOfExplicitUnlockCallIsEffectivelyFinal(node, methodElement, recvTree);
        // Handle acquiring of explicit locks. Verify that the lock expression is effectively
        // final.
        // If the method causes expression "this" or "#1" to be locked, verify that those
        // expressions are effectively final.  TODO: generalize to any expression. This is
        // currently designed only to support methods in ReentrantLock and
        // ReentrantReadWriteLock (which use the "this" expression), as well as Thread.holdsLock
        // (which uses the "#1" expression).
        AnnotationMirror ensuresLockHeldAnno = atypeFactory.getDeclAnnotation(methodElement, EnsuresLockHeld.class);
        List<String> expressions = new ArrayList<>();
        if (ensuresLockHeldAnno != null) {
            expressions.addAll(AnnotationUtils.getElementValueArray(ensuresLockHeldAnno, "value", String.class, false));
        }
        AnnotationMirror ensuresLockHeldIfAnno = atypeFactory.getDeclAnnotation(methodElement, EnsuresLockHeldIf.class);
        if (ensuresLockHeldIfAnno != null) {
            expressions.addAll(AnnotationUtils.getElementValueArray(ensuresLockHeldIfAnno, "expression", String.class, false));
        }
        for (String expr : expressions) {
            if (expr.equals("this")) {
                // are also final. So nothing to be checked for them.
                if (recvTree != null) {
                    ensureExpressionIsEffectivelyFinal(recvTree);
                }
            } else if (expr.equals("#1")) {
                ExpressionTree firstParameter = node.getArguments().get(0);
                if (firstParameter != null) {
                    ensureExpressionIsEffectivelyFinal(firstParameter);
                }
            }
        }
    }
    // Check that matching @GuardSatisfied(index) on a method's formal receiver/parameters
    // matches
    // those in corresponding locations on the method call site.
    Pair<AnnotatedExecutableType, List<AnnotatedTypeMirror>> mfuPair = atypeFactory.methodFromUse(node);
    AnnotatedExecutableType invokedMethod = mfuPair.first;
    List<AnnotatedTypeMirror> requiredArgs = AnnotatedTypes.expandVarArgs(atypeFactory, invokedMethod, node.getArguments());
    // Index on @GuardSatisfied at each location. -1 when no @GuardSatisfied annotation was
    // present.
    // Note that @GuardSatisfied with no index is normally represented as having index -1.
    // We would like to ignore a @GuardSatisfied with no index for these purposes, so if it is
    // encountered we leave its index as -1.
    // The first element of the array is reserved for the receiver.
    int[] guardSatisfiedIndex = // + 1 for the receiver parameter type
    new int[requiredArgs.size() + 1];
    // Retrieve receiver types from method definition and method call
    guardSatisfiedIndex[0] = -1;
    AnnotatedTypeMirror methodDefinitionReceiver = null;
    AnnotatedTypeMirror methodCallReceiver = null;
    ExecutableElement invokedMethodElement = invokedMethod.getElement();
    if (!ElementUtils.isStatic(invokedMethodElement) && invokedMethod.getElement().getKind() != ElementKind.CONSTRUCTOR) {
        methodDefinitionReceiver = invokedMethod.getReceiverType();
        if (methodDefinitionReceiver != null && methodDefinitionReceiver.hasAnnotation(checkerGuardSatisfiedClass)) {
            guardSatisfiedIndex[0] = atypeFactory.getGuardSatisfiedIndex(methodDefinitionReceiver);
            methodCallReceiver = atypeFactory.getReceiverType(node);
        }
    }
    for (int i = 0; i < requiredArgs.size(); i++) {
        guardSatisfiedIndex[i + 1] = -1;
        AnnotatedTypeMirror arg = requiredArgs.get(i);
        if (arg.hasAnnotation(checkerGuardSatisfiedClass)) {
            guardSatisfiedIndex[i + 1] = atypeFactory.getGuardSatisfiedIndex(arg);
        }
    }
    // Combine all of the actual parameters into one list of AnnotationMirrors
    ArrayList<AnnotationMirror> passedArgAnnotations = new ArrayList<>(guardSatisfiedIndex.length);
    passedArgAnnotations.add(methodCallReceiver == null ? null : methodCallReceiver.getAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN));
    for (ExpressionTree tree : node.getArguments()) {
        passedArgAnnotations.add(atypeFactory.getAnnotatedType(tree).getAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN));
    }
    for (int i = 0; i < guardSatisfiedIndex.length; i++) {
        if (guardSatisfiedIndex[i] != -1) {
            for (int j = i + 1; j < guardSatisfiedIndex.length; j++) {
                if (guardSatisfiedIndex[i] == guardSatisfiedIndex[j]) {
                    // The @GuardedBy/@GuardSatisfied/@GuardedByUnknown/@GuardedByBottom
                    // annotations must be identical on the corresponding actual parameters.
                    AnnotationMirror arg1Anno = passedArgAnnotations.get(i);
                    AnnotationMirror arg2Anno = passedArgAnnotations.get(j);
                    if (arg1Anno != null && arg2Anno != null) {
                        boolean bothAreGSwithNoIndex = false;
                        if (AnnotationUtils.areSameByClass(arg1Anno, checkerGuardSatisfiedClass) && AnnotationUtils.areSameByClass(arg2Anno, checkerGuardSatisfiedClass)) {
                            if (atypeFactory.getGuardSatisfiedIndex(arg1Anno) == -1 && atypeFactory.getGuardSatisfiedIndex(arg2Anno) == -1) {
                                // Generally speaking, two @GuardSatisfied annotations with no
                                // index are incomparable.
                                // TODO: If they come from the same variable, they are
                                // comparable.  Fix and add a test case.
                                bothAreGSwithNoIndex = true;
                            }
                        }
                        if (bothAreGSwithNoIndex || !(atypeFactory.getQualifierHierarchy().isSubtype(arg1Anno, arg2Anno) || atypeFactory.getQualifierHierarchy().isSubtype(arg2Anno, arg1Anno))) {
                            // TODO: allow these strings to be localized
                            String formalParam1 = null;
                            if (i == 0) {
                                formalParam1 = "The receiver type";
                            } else {
                                formalParam1 = "Parameter #" + // i, not i-1, so the index is 1-based
                                i;
                            }
                            String formalParam2 = // j, not j-1, so the index is 1-based
                            "parameter #" + j;
                            checker.report(Result.failure("guardsatisfied.parameters.must.match", formalParam1, formalParam2, invokedMethod.toString(), guardSatisfiedIndex[i], arg1Anno, arg2Anno), node);
                        }
                    }
                }
            }
        }
    }
    return super.visitMethodInvocation(node, p);
}
Also used : MethodTree(com.sun.source.tree.MethodTree) ExecutableElement(javax.lang.model.element.ExecutableElement) ArrayList(java.util.ArrayList) SideEffectAnnotation(org.checkerframework.checker.lock.LockAnnotatedTypeFactory.SideEffectAnnotation) AnnotatedTypeMirror(org.checkerframework.framework.type.AnnotatedTypeMirror) AnnotationMirror(javax.lang.model.element.AnnotationMirror) AnnotatedExecutableType(org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType) ExpressionTree(com.sun.source.tree.ExpressionTree) List(java.util.List) ArrayList(java.util.ArrayList)

Example 3 with SideEffectAnnotation

use of org.checkerframework.checker.lock.LockAnnotatedTypeFactory.SideEffectAnnotation in project checker-framework by typetools.

the class LockVisitor method visitMethod.

/**
 * Issues an error if a method (explicitly or implicitly) annotated with @MayReleaseLocks has a
 * formal parameter or receiver (explicitly or implicitly) annotated with @GuardSatisfied. Also
 * issues an error if a synchronized method has a @LockingFree, @SideEffectFree or @Pure
 * annotation.
 *
 * @param node the MethodTree of the method definition to visit
 */
@Override
public Void visitMethod(MethodTree node, Void p) {
    ExecutableElement methodElement = TreeUtils.elementFromDeclaration(node);
    issueErrorIfMoreThanOneLockPreconditionMethodAnnotationPresent(methodElement, node);
    SideEffectAnnotation sea = atypeFactory.methodSideEffectAnnotation(methodElement, true);
    if (sea == SideEffectAnnotation.MAYRELEASELOCKS) {
        boolean issueGSwithMRLWarning = false;
        VariableTree receiver = node.getReceiverParameter();
        if (receiver != null) {
            if (atypeFactory.getAnnotatedType(receiver).hasAnnotation(checkerGuardSatisfiedClass)) {
                issueGSwithMRLWarning = true;
            }
        }
        if (!issueGSwithMRLWarning) {
            // Skip loop if we already decided to issue the warning.
            for (VariableTree vt : node.getParameters()) {
                if (atypeFactory.getAnnotatedType(vt).hasAnnotation(checkerGuardSatisfiedClass)) {
                    issueGSwithMRLWarning = true;
                    break;
                }
            }
        }
        if (issueGSwithMRLWarning) {
            checker.report(Result.failure("guardsatisfied.with.mayreleaselocks"), node);
        }
    }
    // @GuardSatisfied without an index.
    if (methodElement != null && methodElement.getKind() != ElementKind.CONSTRUCTOR) {
        AnnotatedTypeMirror returnTypeATM = atypeFactory.getAnnotatedType(node).getReturnType();
        if (returnTypeATM != null && returnTypeATM.hasAnnotation(GuardSatisfied.class)) {
            int returnGuardSatisfiedIndex = atypeFactory.getGuardSatisfiedIndex(returnTypeATM);
            if (returnGuardSatisfiedIndex == -1) {
                checker.report(Result.failure("guardsatisfied.return.must.have.index"), node);
            }
        }
    }
    if (!sea.isWeakerThan(SideEffectAnnotation.LOCKINGFREE) && methodElement.getModifiers().contains(Modifier.SYNCHRONIZED)) {
        checker.report(Result.failure("lockingfree.synchronized.method", sea), node);
    }
    return super.visitMethod(node, p);
}
Also used : ExecutableElement(javax.lang.model.element.ExecutableElement) VariableTree(com.sun.source.tree.VariableTree) SideEffectAnnotation(org.checkerframework.checker.lock.LockAnnotatedTypeFactory.SideEffectAnnotation) AnnotatedTypeMirror(org.checkerframework.framework.type.AnnotatedTypeMirror) GuardSatisfied(org.checkerframework.checker.lock.qual.GuardSatisfied)

Example 4 with SideEffectAnnotation

use of org.checkerframework.checker.lock.LockAnnotatedTypeFactory.SideEffectAnnotation in project checker-framework by typetools.

the class LockVisitor method checkOverride.

/**
 * Ensures that subclass methods are annotated with a stronger or equally strong side effect
 * annotation than the parent class method.
 */
@Override
protected boolean checkOverride(MethodTree overriderTree, AnnotatedDeclaredType enclosingType, AnnotatedExecutableType overridden, AnnotatedDeclaredType overriddenType) {
    boolean isValid = true;
    SideEffectAnnotation seaOfOverriderMethod = atypeFactory.methodSideEffectAnnotation(TreeUtils.elementFromDeclaration(overriderTree), false);
    SideEffectAnnotation seaOfOverridenMethod = atypeFactory.methodSideEffectAnnotation(overridden.getElement(), false);
    if (seaOfOverriderMethod.isWeakerThan(seaOfOverridenMethod)) {
        isValid = false;
        reportFailure("override.sideeffect.invalid", overriderTree, enclosingType, overridden, overriddenType, null, null);
    }
    return super.checkOverride(overriderTree, enclosingType, overridden, overriddenType) && isValid;
}
Also used : SideEffectAnnotation(org.checkerframework.checker.lock.LockAnnotatedTypeFactory.SideEffectAnnotation)

Aggregations

SideEffectAnnotation (org.checkerframework.checker.lock.LockAnnotatedTypeFactory.SideEffectAnnotation)4 ExecutableElement (javax.lang.model.element.ExecutableElement)3 AnnotatedTypeMirror (org.checkerframework.framework.type.AnnotatedTypeMirror)3 ExpressionTree (com.sun.source.tree.ExpressionTree)2 MethodTree (com.sun.source.tree.MethodTree)2 VariableTree (com.sun.source.tree.VariableTree)1 ArrayList (java.util.ArrayList)1 List (java.util.List)1 ProcessingEnvironment (javax.annotation.processing.ProcessingEnvironment)1 AnnotationMirror (javax.lang.model.element.AnnotationMirror)1 TypeMirror (javax.lang.model.type.TypeMirror)1 GuardSatisfied (org.checkerframework.checker.lock.qual.GuardSatisfied)1 AnnotatedExecutableType (org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType)1