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

org.checkerframework.checker.lock.LockVisitor 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-eisop4
Show newest version
package org.checkerframework.checker.lock;

import com.sun.source.tree.AnnotatedTypeTree;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ArrayAccessTree;
import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.CompoundAssignmentTree;
import com.sun.source.tree.ExpressionTree;
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.SynchronizedTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;

import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
import org.checkerframework.checker.lock.LockAnnotatedTypeFactory.SideEffectAnnotation;
import org.checkerframework.checker.lock.qual.EnsuresLockHeld;
import org.checkerframework.checker.lock.qual.EnsuresLockHeldIf;
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.Holding;
import org.checkerframework.checker.lock.qual.LockHeld;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.common.basetype.BaseTypeVisitor;
import org.checkerframework.dataflow.expression.JavaExpression;
import org.checkerframework.dataflow.expression.Unknown;
import org.checkerframework.dataflow.qual.Deterministic;
import org.checkerframework.dataflow.qual.Pure;
import org.checkerframework.framework.flow.CFAbstractValue;
import org.checkerframework.framework.type.AnnotatedTypeFactory.ParameterizedExecutableType;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
import org.checkerframework.framework.util.JavaExpressionParseUtil.JavaExpressionParseException;
import org.checkerframework.framework.util.StringToJavaExpression;
import org.checkerframework.framework.util.dependenttypes.DependentTypesError;
import org.checkerframework.javacutil.AnnotationMirrorSet;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.TreePathUtil;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypeSystemError;
import org.checkerframework.javacutil.TypesUtils;
import org.plumelib.util.CollectionsPlume;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.processing.ProcessingEnvironment;
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.Modifier;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;

/**
 * The LockVisitor enforces the special type-checking rules described in the Lock Checker manual
 * chapter.
 *
 * @checker_framework.manual #lock-checker Lock Checker
 */
public class LockVisitor extends BaseTypeVisitor {
    /** The class of GuardedBy */
    private static final Class checkerGuardedByClass = GuardedBy.class;

    /** The class of GuardSatisfied */
    private static final Class checkerGuardSatisfiedClass =
            GuardSatisfied.class;

    /** A pattern for spotting self receiver */
    protected static final Pattern SELF_RECEIVER_PATTERN = Pattern.compile("^(\\.(.*))?$");

    /**
     * Constructs a {@link LockVisitor}.
     *
     * @param checker the type checker to use
     */
    public LockVisitor(BaseTypeChecker checker) {
        super(checker);
    }

    @Override
    public Void visitVariable(VariableTree tree, Void p) { // visit a variable declaration
        // A user may not annotate a primitive type, a boxed primitive type or a String
        // with any qualifier from the @GuardedBy hierarchy.
        // They are immutable, so there is no need to guard them.

        TypeMirror tm = TreeUtils.typeOf(tree);

        if (TypesUtils.isBoxedPrimitive(tm)
                || TypesUtils.isPrimitive(tm)
                || TypesUtils.isString(tm)) {
            AnnotatedTypeMirror atm = atypeFactory.getAnnotatedType(tree);
            if (atm.hasExplicitAnnotationRelaxed(atypeFactory.GUARDSATISFIED)
                    || atm.hasExplicitAnnotationRelaxed(atypeFactory.GUARDEDBY)
                    || atm.hasExplicitAnnotation(atypeFactory.GUARDEDBYUNKNOWN)
                    || atm.hasExplicitAnnotation(atypeFactory.GUARDEDBYBOTTOM)) {
                checker.reportError(tree, "immutable.type.guardedby");
            }
        }

        issueErrorIfMoreThanOneGuardedByAnnotationPresent(tree);

        return super.visitVariable(tree, p);
    }

    /**
     * Issues an error if two or more of the following annotations are present on a variable
     * declaration.
     *
     * 
    *
  • {@code @org.checkerframework.checker.lock.qual.GuardedBy} *
  • {@code @net.jcip.annotations.GuardedBy} *
  • {@code @javax.annotation.concurrent.GuardedBy} *
* * @param variableTree the VariableTree for the variable declaration used to determine if * multiple @GuardedBy annotations are present and to report the error */ private void issueErrorIfMoreThanOneGuardedByAnnotationPresent(VariableTree variableTree) { int guardedByAnnotationCount = 0; List annos = TreeUtils.annotationsFromTypeAnnotationTrees( variableTree.getModifiers().getAnnotations()); for (AnnotationMirror anno : annos) { if (atypeFactory.areSameByClass(anno, GuardedBy.class) || AnnotationUtils.areSameByName(anno, "net.jcip.annotations.GuardedBy") || AnnotationUtils.areSameByName( anno, "javax.annotation.concurrent.GuardedBy")) { guardedByAnnotationCount++; if (guardedByAnnotationCount > 1) { checker.reportError(variableTree, "multiple.guardedby.annotations"); return; } } } } @Override public LockAnnotatedTypeFactory createTypeFactory() { return new LockAnnotatedTypeFactory(checker); } /** * 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 tree the MethodTree of the method definition to visit */ @Override public Void visitMethod(MethodTree tree, Void p) { ExecutableElement methodElement = TreeUtils.elementFromDeclaration(tree); issueErrorIfMoreThanOneLockPreconditionMethodAnnotationPresent(methodElement, tree); SideEffectAnnotation sea = atypeFactory.methodSideEffectAnnotation(methodElement, true); if (sea == SideEffectAnnotation.MAYRELEASELOCKS) { boolean issueGSwithMRLWarning = false; VariableTree receiver = tree.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 : tree.getParameters()) { if (atypeFactory .getAnnotatedType(vt) .hasAnnotation(checkerGuardSatisfiedClass)) { issueGSwithMRLWarning = true; break; } } } if (issueGSwithMRLWarning) { checker.reportError(tree, "guardsatisfied.with.mayreleaselocks"); } } // Issue an error if a non-constructor method definition has a return type of // @GuardSatisfied without an index. if (methodElement != null && methodElement.getKind() != ElementKind.CONSTRUCTOR) { AnnotatedTypeMirror returnTypeATM = atypeFactory.getAnnotatedType(tree).getReturnType(); if (returnTypeATM != null && returnTypeATM.hasAnnotation(GuardSatisfied.class)) { int returnGuardSatisfiedIndex = atypeFactory.getGuardSatisfiedIndex(returnTypeATM); if (returnGuardSatisfiedIndex == -1) { checker.reportError(tree, "guardsatisfied.return.must.have.index"); } } } if (!sea.isWeakerThan(SideEffectAnnotation.LOCKINGFREE) && methodElement.getModifiers().contains(Modifier.SYNCHRONIZED)) { checker.reportError(tree, "lockingfree.synchronized.method", sea); } return super.visitMethod(tree, p); } /** * Issues an error if two or more of the following annotations are present on a method. * *
    *
  • {@code @Holding} *
  • {@code @net.jcip.annotations.GuardedBy} *
  • {@code @javax.annotation.concurrent.GuardedBy} *
* * @param methodElement the ExecutableElement for the method call referred to by {@code tree} * @param treeForErrorReporting the MethodTree used to report the error */ private void issueErrorIfMoreThanOneLockPreconditionMethodAnnotationPresent( ExecutableElement methodElement, MethodTree treeForErrorReporting) { int lockPreconditionAnnotationCount = 0; if (atypeFactory.getDeclAnnotation(methodElement, Holding.class) != null) { lockPreconditionAnnotationCount++; } try { if (atypeFactory.jcipGuardedBy != null && atypeFactory.getDeclAnnotation(methodElement, atypeFactory.jcipGuardedBy) != null) { lockPreconditionAnnotationCount++; } if (lockPreconditionAnnotationCount < 2 && atypeFactory.javaxGuardedBy != null && atypeFactory.getDeclAnnotation(methodElement, atypeFactory.javaxGuardedBy) != null) { lockPreconditionAnnotationCount++; } } catch (Exception e) { // Ignore exceptions from Class.forName } if (lockPreconditionAnnotationCount > 1) { checker.reportError(treeForErrorReporting, "multiple.lock.precondition.annotations"); } } /** * When visiting a method call, if the receiver formal parameter has type @GuardSatisfied and * the receiver actual parameter has type @GuardedBy(...), this method verifies that the guard * is satisfied, and it returns true, indicating that the receiver subtype check should be * skipped. If the receiver actual parameter has type @GuardSatisfied, this method simply * returns true without performing any other actions. The method returns false otherwise. * * @param methodInvocationTree the MethodInvocationTree of the method being called * @param methodDefinitionReceiver the ATM of the formal receiver parameter of the method being * called * @param methodCallReceiver the ATM of the receiver argument of the method call * @return whether the caller can skip the receiver subtype check */ @Override protected boolean skipReceiverSubtypeCheck( MethodInvocationTree methodInvocationTree, AnnotatedTypeMirror methodDefinitionReceiver, AnnotatedTypeMirror methodCallReceiver) { AnnotationMirror primaryGb = methodCallReceiver.getAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN); AnnotationMirror effectiveGb = methodCallReceiver.getEffectiveAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN); // If the receiver actual parameter has type @GuardSatisfied, skip the subtype check. // Consider only a @GuardSatisfied primary annotation - hence use primaryGb instead of // effectiveGb. if (primaryGb != null && atypeFactory.areSameByClass(primaryGb, checkerGuardSatisfiedClass)) { AnnotationMirror primaryGbOnMethodDefinition = methodDefinitionReceiver.getAnnotationInHierarchy( atypeFactory.GUARDEDBYUNKNOWN); if (primaryGbOnMethodDefinition != null && atypeFactory.areSameByClass( primaryGbOnMethodDefinition, checkerGuardSatisfiedClass)) { return true; } } if (atypeFactory.areSameByClass(effectiveGb, checkerGuardedByClass)) { AnnotationMirrorSet annos = methodDefinitionReceiver.getAnnotations(); AnnotationMirror guardSatisfied = atypeFactory.getAnnotationByClass(annos, checkerGuardSatisfiedClass); if (guardSatisfied != null) { ExpressionTree receiverTree = TreeUtils.getReceiverTree(methodInvocationTree); if (receiverTree == null) { checkLockOfImplicitThis(methodInvocationTree, effectiveGb); } else { checkLock(receiverTree, effectiveGb); } return true; } } return false; } @Override protected AnnotationMirrorSet getExceptionParameterLowerBoundAnnotations() { AnnotationMirrorSet tops = qualHierarchy.getTopAnnotations(); AnnotationMirrorSet annotationSet = new AnnotationMirrorSet(); for (AnnotationMirror anno : tops) { if (AnnotationUtils.areSame(anno, atypeFactory.GUARDEDBYUNKNOWN)) { annotationSet.add(atypeFactory.GUARDEDBY); } else { annotationSet.add(anno); } } return annotationSet; } @Override protected void checkConstructorResult( AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { // Newly created objects are guarded by nothing, so allow @GuardedBy({}) on constructor // results. AnnotationMirror anno = constructorType .getReturnType() .getAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN); if (AnnotationUtils.areSame(anno, atypeFactory.GUARDEDBYUNKNOWN) || AnnotationUtils.areSame(anno, atypeFactory.GUARDEDBYBOTTOM)) { checker.reportWarning(constructorElement, "inconsistent.constructor.type", anno, null); } } @Override protected boolean commonAssignmentCheck( AnnotatedTypeMirror varType, AnnotatedTypeMirror valueType, Tree valueTree, @CompilerMessageKey String errorKey, Object... extraArgs) { // In cases where assigning a value with a @GuardedBy annotation to a variable with a // @GuardSatisfied annotation is legal, this is our last chance to check that the // appropriate locks are held before the information in the @GuardedBy annotation is lost in // the assignment to the variable annotated with @GuardSatisfied. See the discussion of // @GuardSatisfied in the "Type-checking rules" section of the Lock Checker manual chapter // for more details. boolean result = true; if (varType.hasAnnotation(GuardSatisfied.class)) { if (valueType.hasAnnotation(GuardedBy.class)) { return checkLock(valueTree, valueType.getAnnotation(GuardedBy.class)); } else if (valueType.hasAnnotation(GuardSatisfied.class)) { // TODO: Find a cleaner, non-abstraction-breaking way to know whether method actual // parameters are being assigned to formal parameters. if (!errorKey.equals("argument.type.incompatible")) { // If both @GuardSatisfied have no index, the assignment is not allowed because // the LHS and RHS expressions may be guarded by different lock expressions. // The assignment is allowed when matching a formal parameter to an actual // parameter (see the if block above). int varTypeGuardSatisfiedIndex = atypeFactory.getGuardSatisfiedIndex(varType); int valueTypeGuardSatisfiedIndex = atypeFactory.getGuardSatisfiedIndex(valueType); if (varTypeGuardSatisfiedIndex == -1 && valueTypeGuardSatisfiedIndex == -1) { checker.reportError( valueTree, "guardsatisfied.assignment.disallowed", varType, valueType); result = false; } } else { // The RHS can be @GuardSatisfied with a different index when matching method // formal parameters to actual parameters. // The actual matching is done in LockVisitor.visitMethodInvocation and a // guardsatisfied.parameters.must.match error // is issued if the parameters do not match exactly. // Do nothing here, since there is no precondition to be checked on a // @GuardSatisfied parameter. // Note: this matching of a @GS(index) to a @GS(differentIndex) is *only* // allowed when matching method formal parameters to actual parameters. return true; } } else if (!atypeFactory.getTypeHierarchy().isSubtype(valueType, varType)) { // Special case: replace the @GuardSatisfied primary annotation on the LHS with // @GuardedBy({}) and see if it type checks. AnnotatedTypeMirror varType2 = varType.deepCopy(); // TODO: Would shallowCopy be sufficient? varType2.replaceAnnotation(atypeFactory.GUARDEDBY); if (atypeFactory.getTypeHierarchy().isSubtype(valueType, varType2)) { return true; } } } result = super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs) && result; return result; } @Override public Void visitMemberSelect(MemberSelectTree tree, Void p) { if (TreeUtils.isFieldAccess(tree)) { AnnotatedTypeMirror atmOfReceiver = atypeFactory.getAnnotatedType(tree.getExpression()); // The atmOfReceiver for "void.class" is TypeKind.VOID, which isn't annotated so avoid // it. if (atmOfReceiver.getKind() != TypeKind.VOID) { AnnotationMirror gb = atmOfReceiver.getEffectiveAnnotationInHierarchy( atypeFactory.GUARDEDBYUNKNOWN); checkLock(tree.getExpression(), gb); } } return super.visitMemberSelect(tree, p); } private void reportFailure( @CompilerMessageKey String messageKey, MethodTree overriderTree, AnnotatedDeclaredType enclosingType, AnnotatedExecutableType overridden, AnnotatedDeclaredType overriddenType, List overriderLocks, List overriddenLocks) { // Get the type of the overriding method. AnnotatedExecutableType overrider = atypeFactory.getAnnotatedType(overriderTree); if (overrider.getTypeVariables().isEmpty() && !overridden.getTypeVariables().isEmpty()) { overridden = overridden.getErased(); } String overriderMeth = overrider.toString(); String overriderTyp = enclosingType.getUnderlyingType().asElement().toString(); String overriddenMeth = overridden.toString(); String overriddenTyp = overriddenType.getUnderlyingType().asElement().toString(); if (overriderLocks == null || overriddenLocks == null) { checker.reportError( overriderTree, messageKey, overriderTyp, overriderMeth, overriddenTyp, overriddenMeth); } else { checker.reportError( overriderTree, messageKey, overriderTyp, overriderMeth, overriddenTyp, overriddenMeth, overriderLocks, overriddenLocks); } } /** * 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 overriddenMethodType, AnnotatedDeclaredType overriddenType) { boolean isValid = true; SideEffectAnnotation seaOfOverriderMethod = atypeFactory.methodSideEffectAnnotation( TreeUtils.elementFromDeclaration(overriderTree), false); SideEffectAnnotation seaOfOverriddenMethod = atypeFactory.methodSideEffectAnnotation(overriddenMethodType.getElement(), false); if (seaOfOverriderMethod.isWeakerThan(seaOfOverriddenMethod)) { isValid = false; reportFailure( "override.sideeffect.invalid", overriderTree, enclosingType, overriddenMethodType, overriddenType, null, null); } return super.checkOverride( overriderTree, enclosingType, overriddenMethodType, overriddenType) && isValid; } @Override public Void visitArrayAccess(ArrayAccessTree tree, Void p) { AnnotatedTypeMirror atmOfReceiver = atypeFactory.getAnnotatedType(tree.getExpression()); AnnotationMirror gb = atmOfReceiver.getEffectiveAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN); checkLock(tree.getExpression(), gb); return super.visitArrayAccess(tree, p); } /** * Skips the call to super and returns true. * *

{@code GuardedBy({})} is the default type on class declarations, which is a subtype of the * top annotation {@code @GuardedByUnknown}. However, it is valid to declare an instance of a * class with any annotation from the {@code @GuardedBy} hierarchy. Hence, this method returns * true for annotations in the {@code @GuardedBy} hierarchy. * *

Also returns true for annotations in the {@code @LockPossiblyHeld} hierarchy since the * default for that hierarchy is the top type and annotations from that hierarchy cannot be * explicitly written in code. */ @Override public boolean isValidUse( AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType, Tree tree) { return true; } /** * 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 methodInvocationTree the MethodInvocationTree of the method call being visited */ @Override public Void visitMethodInvocation(MethodInvocationTree methodInvocationTree, Void p) { ExecutableElement methodElement = TreeUtils.elementFromUse(methodInvocationTree); SideEffectAnnotation seaOfInvokedMethod = atypeFactory.methodSideEffectAnnotation(methodElement, false); MethodTree enclosingMethod = TreePathUtil.enclosingMethod(atypeFactory.getPath(methodInvocationTree)); ExecutableElement enclosingMethodElement = null; if (enclosingMethod != null) { enclosingMethodElement = TreeUtils.elementFromDeclaration(enclosingMethod); } if (enclosingMethodElement != null) { SideEffectAnnotation seaOfContainingMethod = atypeFactory.methodSideEffectAnnotation(enclosingMethodElement, false); if (seaOfInvokedMethod.isWeakerThan(seaOfContainingMethod)) { checker.reportError( methodInvocationTree, "method.guarantee.violated", seaOfContainingMethod.getNameOfSideEffectAnnotation(), enclosingMethodElement.getSimpleName(), methodElement.getSimpleName(), seaOfInvokedMethod.getNameOfSideEffectAnnotation()); } } if (methodElement != null) { // Handle releasing of explicit locks. Verify that the lock expression is effectively // final. ExpressionTree receiverTree = TreeUtils.getReceiverTree(methodInvocationTree); ensureReceiverOfExplicitUnlockCallIsEffectivelyFinal(methodElement, receiverTree); // 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 expressions = new ArrayList<>(); if (ensuresLockHeldAnno != null) { expressions.addAll( AnnotationUtils.getElementValueArray( ensuresLockHeldAnno, atypeFactory.ensuresLockHeldValueElement, String.class)); } AnnotationMirror ensuresLockHeldIfAnno = atypeFactory.getDeclAnnotation(methodElement, EnsuresLockHeldIf.class); if (ensuresLockHeldIfAnno != null) { expressions.addAll( AnnotationUtils.getElementValueArray( ensuresLockHeldIfAnno, atypeFactory.ensuresLockHeldIfExpressionElement, String.class)); } for (String expr : expressions) { if (expr.equals("this")) { // receiverTree will be null for implicit this, or class name receivers. But // they are also final. So nothing to be checked for them. if (receiverTree != null) { ensureExpressionIsEffectivelyFinal(receiverTree); } } else if (expr.equals("#1")) { ExpressionTree firstParameter = methodInvocationTree.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. ParameterizedExecutableType mType = atypeFactory.methodFromUse(methodInvocationTree); AnnotatedExecutableType invokedMethod = mType.executableType; List paramTypes = invokedMethod.getParameterTypes(); // 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[] = new int[paramTypes.size() + 1]; // + 1 for the receiver parameter type // 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(methodInvocationTree); } } // Retrieve formal parameter types from the method definition. for (int i = 0; i < paramTypes.size(); i++) { guardSatisfiedIndex[i + 1] = -1; AnnotatedTypeMirror paramType = paramTypes.get(i); if (paramType.hasAnnotation(checkerGuardSatisfiedClass)) { guardSatisfiedIndex[i + 1] = atypeFactory.getGuardSatisfiedIndex(paramType); } } // Combine all of the actual parameters into one list of AnnotationMirrors. ArrayList passedArgTypes = new ArrayList<>(guardSatisfiedIndex.length); passedArgTypes.add(methodCallReceiver); for (ExpressionTree argTree : methodInvocationTree.getArguments()) { AnnotatedTypeMirror argType = atypeFactory.getAnnotatedType(argTree); passedArgTypes.add(argType); } ArrayList passedArgAnnotations = new ArrayList<>(guardSatisfiedIndex.length); for (AnnotatedTypeMirror atm : passedArgTypes) { if (atm != null) { passedArgAnnotations.add( atm.getAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN)); } else { passedArgAnnotations.add(null); } } // Perform the validity check and issue an error if not valid. 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 (atypeFactory.areSameByClass(arg1Anno, checkerGuardSatisfiedClass) && atypeFactory.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; } } TypeMirror arg1TM = passedArgTypes.get(i).getUnderlyingType(); TypeMirror arg2TM = passedArgTypes.get(j).getUnderlyingType(); if (bothAreGSwithNoIndex || !(qualHierarchy.isSubtypeShallow( arg1Anno, arg1TM, arg2Anno, arg2TM) || qualHierarchy.isSubtypeShallow( arg2Anno, arg2TM, arg1Anno, arg1TM))) { String formalParam1; if (i == 0) { formalParam1 = "The receiver type"; } else { formalParam1 = "Parameter #" + i; // i, not i-1, so the index is 1-based } String formalParam2 = "parameter #" + j; // j, not j-1, so the index is 1-based checker.reportError( methodInvocationTree, "guardsatisfied.parameters.must.match", formalParam1, formalParam2, invokedMethod.toString(), guardSatisfiedIndex[i], arg1Anno, arg2Anno); } } } } } } return super.visitMethodInvocation(methodInvocationTree, p); } /** * Issues an error if the receiver of an unlock() call is not effectively final. * * @param methodElement the ExecutableElement for a method call to unlock() * @param lockExpression the receiver tree for the method call to unlock(). Can be null. */ private void ensureReceiverOfExplicitUnlockCallIsEffectivelyFinal( ExecutableElement methodElement, @Nullable ExpressionTree lockExpression) { if (lockExpression == null) { // Implicit this, or class name receivers, are null. But they are also final. So nothing // to be checked for them. return; } if (!methodElement.getSimpleName().contentEquals("unlock")) { return; } TypeMirror lockExpressionType = TreeUtils.typeOf(lockExpression); ProcessingEnvironment processingEnvironment = checker.getProcessingEnvironment(); javax.lang.model.util.Types types = processingEnvironment.getTypeUtils(); // TODO: make a type declaration annotation for this rather than looking for the // Lock.unlock() method explicitly. TypeMirror lockInterfaceTypeMirror = TypesUtils.typeFromClass( Lock.class, types, processingEnvironment.getElementUtils()); if (types.isSubtype(types.erasure(lockExpressionType), lockInterfaceTypeMirror)) { ensureExpressionIsEffectivelyFinal(lockExpression); } } /** * 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. * *

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 tree the SynchronizedTree for the synchronized block being visited */ @Override public Void visitSynchronized(SynchronizedTree tree, 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 = tree.getExpression(); ensureExpressionIsEffectivelyFinal(synchronizedExpression); TypeMirror expressionType = types.erasure( atypeFactory.getAnnotatedType(synchronizedExpression).getUnderlyingType()); if (types.isSubtype(expressionType, lockInterfaceTypeMirror)) { checker.reportError(tree, "explicit.lock.synchronized"); } MethodTree enclosingMethod = TreePathUtil.enclosingMethod(atypeFactory.getPath(tree)); ExecutableElement methodElement = null; if (enclosingMethod != null) { methodElement = TreeUtils.elementFromDeclaration(enclosingMethod); SideEffectAnnotation seaOfContainingMethod = atypeFactory.methodSideEffectAnnotation(methodElement, false); if (!seaOfContainingMethod.isWeakerThan(SideEffectAnnotation.LOCKINGFREE)) { checker.reportError( tree, "synchronized.block.in.lockingfree.method", seaOfContainingMethod); } } return super.visitSynchronized(tree, p); } /** * Ensures that each variable accessed in an expression is final or effectively final and that * each called method in the expression is @Deterministic. Issues an error otherwise. * Recursively performs this check on method arguments. Only intended to be used on the * expression of a synchronized block. * *

Example: given the expression var1.field1.method1(var2.method2()).field2, var1, var2, * field1 and field2 are enforced to be final or effectively final, and method1 and method2 are * enforced to be @Deterministic. * * @param lockExpressionTree the expression tree of a synchronized block * @return true if the check succeeds, false if an error message was issued */ private boolean ensureExpressionIsEffectivelyFinal(ExpressionTree lockExpressionTree) { // This functionality could be implemented using a visitor instead, however with this // design, it is easier to be certain that an error will always be issued if a tree kind is // not recognized. // Only the most common tree kinds for synchronized expressions are supported. // Traverse the expression using 'tree', as 'lockExpressionTree' is used for error // reporting. ExpressionTree tree = lockExpressionTree; boolean result = true; while (true) { tree = TreeUtils.withoutParens(tree); switch (tree.getKind()) { case MEMBER_SELECT: if (!isTreeSymbolEffectivelyFinalOrUnmodifiable(tree)) { checker.reportError(tree, "lock.expression.not.final", lockExpressionTree); return false; } tree = ((MemberSelectTree) tree).getExpression(); break; case IDENTIFIER: if (!isTreeSymbolEffectivelyFinalOrUnmodifiable(tree)) { checker.reportError(tree, "lock.expression.not.final", lockExpressionTree); return false; } return result; case METHOD_INVOCATION: Element elem = TreeUtils.elementFromUse(tree); if (atypeFactory.getDeclAnnotationNoAliases(elem, Deterministic.class) == null && atypeFactory.getDeclAnnotationNoAliases(elem, Pure.class) == null) { checker.reportError(tree, "lock.expression.not.final", lockExpressionTree); return false; } MethodInvocationTree methodInvocationTree = (MethodInvocationTree) tree; for (ExpressionTree argTree : methodInvocationTree.getArguments()) { result = ensureExpressionIsEffectivelyFinal(argTree) && result; } tree = methodInvocationTree.getMethodSelect(); break; default: checker.reportError( tree, "lock.expression.possibly.not.final", lockExpressionTree); return false; } } } /** * Issues an error if the given expression is not effectively final. Returns true if the * expression is effectively final, false if an error was issued. * * @param lockExpr an expression that might be effectively final * @param expressionForErrorReporting how to print the expression in an error message * @param treeForErrorReporting where to report the error * @return true if the expression is effectively final, false if an error was issued */ private boolean ensureExpressionIsEffectivelyFinal( JavaExpression lockExpr, String expressionForErrorReporting, Tree treeForErrorReporting) { boolean result = atypeFactory.isExpressionEffectivelyFinal(lockExpr); if (!result) { checker.reportError( treeForErrorReporting, "lock.expression.not.final", expressionForErrorReporting); } return result; } @Override public Void visitAnnotation(AnnotationTree tree, Void p) { ArrayList annotationTreeList = new ArrayList<>(1); annotationTreeList.add(tree); List amList = TreeUtils.annotationsFromTypeAnnotationTrees(annotationTreeList); if (amList != null) { for (AnnotationMirror annotationMirror : amList) { if (atypeFactory.areSameByClass(annotationMirror, checkerGuardSatisfiedClass)) { issueErrorIfGuardSatisfiedAnnotationInUnsupportedLocation(tree); } } } return super.visitAnnotation(tree, p); } /** * Issues an error if a GuardSatisfied annotation is found in a location other than a method * return type or parameter (including the receiver). * * @param annotationTree an AnnotationTree used for error reporting and to help determine that * an array parameter has no GuardSatisfied annotations except on the array type */ // TODO: Remove this method once @TargetLocations are enforced (i.e. once // issue https://github.com/typetools/checker-framework/issues/1919 is closed). private void issueErrorIfGuardSatisfiedAnnotationInUnsupportedLocation( AnnotationTree annotationTree) { TreePath currentPath = getCurrentPath(); TreePath path = getPathForLocalVariableRetrieval(currentPath); if (path != null) { Tree tree = path.getLeaf(); Tree.Kind kind = tree.getKind(); if (kind == Tree.Kind.METHOD) { // The @GuardSatisfied annotation is on the return type. return; } else if (kind == Tree.Kind.VARIABLE) { VariableTree varTree = (VariableTree) tree; Tree varTypeTree = varTree.getType(); if (varTypeTree != null) { TreePath parentPath = path.getParentPath(); if (parentPath != null && parentPath.getLeaf().getKind() == Tree.Kind.METHOD) { Tree.Kind varTypeTreeKind = varTypeTree.getKind(); if (varTypeTreeKind == Tree.Kind.ANNOTATED_TYPE) { AnnotatedTypeTree annotatedTypeTree = (AnnotatedTypeTree) varTypeTree; if (annotatedTypeTree.getUnderlyingType().getKind() != Tree.Kind.ARRAY_TYPE || annotatedTypeTree .getAnnotations() .contains(annotationTree)) { // Method parameter return; } } else if (varTypeTreeKind != Tree.Kind.ARRAY_TYPE) { // Method parameter or receiver return; } } } } } checker.reportError(annotationTree, "guardsatisfied.location.disallowed"); } /** * The JavaExpression parser requires a path for retrieving the scope that will be used to * resolve local variables. One would expect that simply providing the path to an AnnotationTree * would work, since the compiler (as called by the org.checkerframework.javacutil.Resolver * class) could walk up the path from the AnnotationTree to determine the scope. Unfortunately * this is not how the compiler works. One must provide the path at the right level (not so deep * that it results in a symbol not being found, but not so high up that it is out of the scope * at hand). This is a problem when trying to retrieve local variables, since one could silently * miss a local variable in scope and accidentally retrieve a field with the same name. This * method returns the correct path for this purpose, given a path to an AnnotationTree. * *

Note: this is definitely necessary for local variable retrieval. It has not been tested * whether this is strictly necessary for fields or other identifiers. * *

Only call this method from visitAnnotation. * * @param path the TreePath whose leaf is an AnnotationTree * @return a TreePath that can be passed to methods in the Resolver class to locate local * variables */ private @Nullable TreePath getPathForLocalVariableRetrieval(TreePath path) { assert path.getLeaf() instanceof AnnotationTree; // TODO: handle annotations in trees of kind NEW_CLASS (and add test coverage for this // scenario). // Currently an annotation in such a tree, such as "new @GuardedBy("foo") Object()", // results in a "constructor.invocation.invalid" error. This must be fixed first. path = path.getParentPath(); if (path == null) { return null; } // A MODIFIERS tree for a VARIABLE or METHOD parent tree would be available at this level, // but it is not directly handled. Instead, its parent tree (one level higher) is handled. // Other tree kinds are also handled one level higher. path = path.getParentPath(); if (path == null) { return null; } Tree tree = path.getLeaf(); Tree.Kind kind = tree.getKind(); switch (kind) { case ARRAY_TYPE: case VARIABLE: case TYPE_CAST: case INSTANCE_OF: case METHOD: case NEW_ARRAY: case TYPE_PARAMETER: // TODO: visitAnnotation does not currently visit annotations on wildcard bounds. // Address this for the Lock Checker somehow and enable these, as well as the // corresponding test cases in ChapterExamples.java // case EXTENDS_WILDCARD: // case SUPER_WILDCARD: return path; default: return null; } } /** * Returns true if the symbol for the given tree is final or effectively final. Package, class * and method symbols are unmodifiable and therefore considered final. * * @param tree the tree to test * @return true if the symbol for the given tree is final or effectively final */ private boolean isTreeSymbolEffectivelyFinalOrUnmodifiable(Tree tree) { Element elem = TreeUtils.elementFromTree(tree); ElementKind ek = elem.getKind(); return ek == ElementKind.PACKAGE || ek == ElementKind.CLASS || ek == ElementKind.METHOD || ElementUtils.isEffectivelyFinal(elem); } @Override @SuppressWarnings("interning:not.interned") // AST node comparison public Void visitIdentifier(IdentifierTree tree, Void p) { // If the identifier is a field accessed via an implicit this, then check the lock of this. // (All other field accesses are checked in visitMemberSelect.) if (TreeUtils.isFieldAccess(tree)) { Tree parent = getCurrentPath().getParentPath().getLeaf(); // If the parent is not a member select, or if it is and the field is the expression, // then the field is accessed via an implicit this. if ((parent.getKind() != Tree.Kind.MEMBER_SELECT || ((MemberSelectTree) parent).getExpression() == tree) && !ElementUtils.isStatic(TreeUtils.elementFromUse(tree))) { AnnotationMirror guardedBy = atypeFactory .getSelfType(tree) .getAnnotationInHierarchy(atypeFactory.GUARDEDBY); checkLockOfImplicitThis(tree, guardedBy); } } return super.visitIdentifier(tree, p); } @Override public Void visitBinary(BinaryTree binaryTree, Void p) { if (TreeUtils.isStringConcatenation(binaryTree)) { ExpressionTree leftTree = binaryTree.getLeftOperand(); ExpressionTree rightTree = binaryTree.getRightOperand(); boolean lhsIsString = TypesUtils.isString(TreeUtils.typeOf(leftTree)); boolean rhsIsString = TypesUtils.isString(TreeUtils.typeOf(rightTree)); if (!lhsIsString) { checkPreconditionsForImplicitToStringCall(leftTree); } else if (!rhsIsString) { checkPreconditionsForImplicitToStringCall(rightTree); } } return super.visitBinary(binaryTree, p); } @Override public Void visitCompoundAssignment(CompoundAssignmentTree tree, Void p) { if (TreeUtils.isStringCompoundConcatenation(tree)) { ExpressionTree rightTree = tree.getExpression(); if (!TypesUtils.isString(TreeUtils.typeOf(rightTree))) { checkPreconditionsForImplicitToStringCall(rightTree); } } return super.visitCompoundAssignment(tree, p); } /** * Checks precondition for {@code tree} that is known to be the receiver of an implicit * toString() call. The receiver of toString() is defined in the annotated JDK to * be @GuardSatisfied. Therefore if the expression is guarded by a set of locks, the locks must * be held prior to this implicit call to toString(). * *

Only call this method from visitBinary and visitCompoundAssignment. * * @param tree the Tree corresponding to the expression that is known to be the receiver of an * implicit toString() call */ // TODO: If and when the de-sugared .toString() tree is accessible from BaseTypeVisitor, // the toString() method call should be visited instead of doing this. This would result // in "contracts.precondition.not.satisfied" errors being issued instead of // "contracts.precondition.not.satisfied.field", so it would be clear that // the error refers to an implicit method call, not a dereference (field access). private void checkPreconditionsForImplicitToStringCall(ExpressionTree tree) { AnnotationMirror gbAnno = atypeFactory .getAnnotatedType(tree) .getEffectiveAnnotationInHierarchy(atypeFactory.GUARDEDBY); checkLock(tree, gbAnno); } private void checkLockOfImplicitThis(Tree tree, AnnotationMirror gbAnno) { checkLockOfThisOrTree(tree, true, gbAnno); } /** * Checks the lock of the given tree. * * @param tree a tree whose lock to check * @param gbAnno a {@code @GuardedBy} annotation * @return true if the check succeeds, false if an error message was issued */ private boolean checkLock(Tree tree, AnnotationMirror gbAnno) { return checkLockOfThisOrTree(tree, false, gbAnno); } /** * Helper method that checks the lock of either the implicit {@code this} or the given tree. * * @param tree a tree whose lock to check * @param implicitThis true if checking the lock of the implicit {@code this} * @param gbAnno a {@code @GuardedBy} annotation * @return true if the check succeeds, false if an error message was issued */ private boolean checkLockOfThisOrTree( Tree tree, boolean implicitThis, AnnotationMirror gbAnno) { if (gbAnno == null) { throw new TypeSystemError("LockVisitor.checkLock: gbAnno cannot be null"); } if (atypeFactory.areSameByClass(gbAnno, GuardedByUnknown.class) || atypeFactory.areSameByClass(gbAnno, GuardedByBottom.class)) { checker.reportError(tree, "lock.not.held", "unknown lock " + gbAnno); return false; } else if (atypeFactory.areSameByClass(gbAnno, GuardSatisfied.class)) { return true; } List expressions = getLockExpressions(implicitThis, gbAnno, tree); if (expressions.isEmpty()) { return true; } boolean result = true; LockStore store = atypeFactory.getStoreBefore(tree); for (LockExpression expression : expressions) { if (expression.error != null) { checker.reportError( tree, "expression.unparsable.type.invalid", expression.error.toString()); result = false; } else if (expression.lockExpression == null) { checker.reportError( tree, "expression.unparsable.type.invalid", expression.expressionString); result = false; } else if (!isLockHeld(expression.lockExpression, store)) { checker.reportError(tree, "lock.not.held", expression.lockExpression.toString()); result = false; } if (expression.error != null && expression.lockExpression != null) { result = ensureExpressionIsEffectivelyFinal( expression.lockExpression, expression.expressionString, tree) && result; } } return result; } private boolean isLockHeld(JavaExpression lockExpr, LockStore store) { if (store == null) { return false; } CFAbstractValue value = store.getValue(lockExpr); if (value == null) { return false; } AnnotationMirrorSet annos = value.getAnnotations(); AnnotationMirror lockAnno = qualHierarchy.findAnnotationInSameHierarchy(annos, atypeFactory.LOCKHELD); return lockAnno != null && atypeFactory.areSameByClass(lockAnno, LockHeld.class); } private List getLockExpressions( boolean implicitThis, AnnotationMirror gbAnno, Tree tree) { List expressions = AnnotationUtils.getElementValueArray( gbAnno, atypeFactory.guardedByValueElement, String.class, Collections.emptyList()); if (expressions.isEmpty()) { return Collections.emptyList(); } TreePath currentPath = getCurrentPath(); TypeMirror enclosingType = TreeUtils.typeOf(TreePathUtil.enclosingClass(currentPath)); JavaExpression pseudoReceiver = JavaExpression.getPseudoReceiver(currentPath, enclosingType); JavaExpression self; if (implicitThis) { self = pseudoReceiver; } else if (TreeUtils.isExpressionTree(tree)) { self = JavaExpression.fromTree((ExpressionTree) tree); } else { self = new Unknown(tree); } return CollectionsPlume.mapList( expression -> parseExpressionString(expression, currentPath, self), expressions); } /** * Parse a Java expression. * * @param expression the Java expression * @param path the path to the expression * @param itself the self expression * @return the parsed expression */ private LockExpression parseExpressionString( String expression, TreePath path, JavaExpression itself) { LockExpression lockExpression = new LockExpression(expression); if (DependentTypesError.isExpressionError(expression)) { lockExpression.error = DependentTypesError.unparse(expression); return lockExpression; } Matcher selfReceiverMatcher = SELF_RECEIVER_PATTERN.matcher(expression); try { if (selfReceiverMatcher.matches()) { String remainingExpression = selfReceiverMatcher.group(2); if (remainingExpression == null || remainingExpression.isEmpty()) { lockExpression.lockExpression = itself; if (!atypeFactory.isExpressionEffectivelyFinal(lockExpression.lockExpression)) { checker.reportError( path.getLeaf(), "lock.expression.not.final", lockExpression.lockExpression); } return lockExpression; } else { lockExpression.lockExpression = StringToJavaExpression.atPath( itself.toString() + "." + remainingExpression, path, checker); if (!atypeFactory.isExpressionEffectivelyFinal(lockExpression.lockExpression)) { checker.reportError( path.getLeaf(), "lock.expression.not.final", lockExpression.lockExpression); } return lockExpression; } } else { lockExpression.lockExpression = StringToJavaExpression.atPath(expression, path, checker); return lockExpression; } } catch (JavaExpressionParseException ex) { lockExpression.error = new DependentTypesError(expression, ex); return lockExpression; } } private static class LockExpression { final String expressionString; JavaExpression lockExpression = null; DependentTypesError error = null; LockExpression(String expression) { this.expressionString = expression; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy