package org.checkerframework.checker.lock;

import static org.checkerframework.javacutil.TreeUtils.getReceiverTree;

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.Tree.Kind;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
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;
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.common.basetype.BaseTypeChecker;
import org.checkerframework.common.basetype.BaseTypeVisitor;
import org.checkerframework.dataflow.analysis.FlowExpressions;
import org.checkerframework.dataflow.analysis.FlowExpressions.Receiver;
import org.checkerframework.dataflow.analysis.FlowExpressions.Unknown;
import org.checkerframework.dataflow.qual.Deterministic;
import org.checkerframework.dataflow.qual.Pure;
import org.checkerframework.framework.flow.CFAbstractValue;
import org.checkerframework.framework.source.Result;
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.type.QualifierHierarchy;
import org.checkerframework.framework.util.AnnotatedTypes;
import org.checkerframework.framework.util.FlowExpressionParseUtil;
import org.checkerframework.framework.util.FlowExpressionParseUtil.FlowExpressionContext;
import org.checkerframework.framework.util.FlowExpressionParseUtil.FlowExpressionParseException;
import org.checkerframework.framework.util.dependenttypes.DependentTypesError;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypesUtils;

/**
 * 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<LockAnnotatedTypeFactory> {
    private final Class<? extends Annotation> checkerGuardedByClass = GuardedBy.class;
    private final Class<? extends Annotation> checkerGuardSatisfiedClass = GuardSatisfied.class;

    protected static final Pattern SELF_RECEIVER_PATTERN = Pattern.compile("^<self>(\\.(.*))?$");

    public LockVisitor(BaseTypeChecker checker) {
        super(checker);
    }

    @Override
    public Void visitVariable(VariableTree node, 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(node);

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

        issueErrorIfMoreThanOneGuardedByAnnotationPresent(node);

        return super.visitVariable(node, p);
    }

    /**
     * Issues an error if two or more of the following annotations are present on a variable
     * declaration.
     *
     * <ul>
     *   <li>{@code @org.checkerframework.checker.lock.qual.GuardedBy}
     *   <li>{@code @net.jcip.annotations.GuardedBy}
     *   <li>{@code @javax.annotation.concurrent.GuardedBy}
     * </ul>
     *
     * @param variableTree the VariableTree for the variable declaration used to determine if
     *     multiple @GuardedBy annotations are present and to report the error via checker.report.
     */
    private void issueErrorIfMoreThanOneGuardedByAnnotationPresent(VariableTree variableTree) {
        int guardedByAnnotationCount = 0;

        List<AnnotationMirror> annos =
                TreeUtils.annotationsFromTypeAnnotationTrees(
                        variableTree.getModifiers().getAnnotations());
        for (AnnotationMirror anno : annos) {
            if (AnnotationUtils.areSameByClass(anno, GuardedBy.class)
                    || AnnotationUtils.areSameByName(anno, "net.jcip.annotations.GuardedBy")
                    || AnnotationUtils.areSameByName(
                            anno, "javax.annotation.concurrent.GuardedBy")) {
                guardedByAnnotationCount++;
                if (guardedByAnnotationCount > 1) {
                    checker.report(Result.failure("multiple.guardedby.annotations"), variableTree);
                    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 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);
            }
        }

        // 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(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);
    }

    /**
     * Issues an error if two or more of the following annotations are present on a method.
     *
     * <ul>
     *   <li>{@code @Holding}
     *   <li>{@code @net.jcip.annotations.GuardedBy}
     *   <li>{@code @javax.annotation.concurrent.GuardedBy}
     * </ul>
     *
     * @param methodElement the ExecutableElement for the method call referred to by {@code node}
     * @param treeForErrorReporting the MethodTree used to report the error via checker.report.
     */
    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.report(
                    Result.failure("multiple.lock.precondition.annotations"),
                    treeForErrorReporting);
        }
    }

    /**
     * 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
                && AnnotationUtils.areSameByClass(primaryGb, checkerGuardSatisfiedClass)) {
            AnnotationMirror primaryGbOnMethodDefinition =
                    methodDefinitionReceiver.getAnnotationInHierarchy(
                            atypeFactory.GUARDEDBYUNKNOWN);
            if (primaryGbOnMethodDefinition != null
                    && AnnotationUtils.areSameByClass(
                            primaryGbOnMethodDefinition, checkerGuardSatisfiedClass)) {
                return true;
            }
        }

        if (AnnotationUtils.areSameByClass(effectiveGb, checkerGuardedByClass)) {
            Set<AnnotationMirror> annos = methodDefinitionReceiver.getAnnotations();
            AnnotationMirror guardSatisfied =
                    AnnotationUtils.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 Set<? extends AnnotationMirror> getExceptionParameterLowerBoundAnnotations() {
        Set<? extends AnnotationMirror> tops =
                atypeFactory.getQualifierHierarchy().getTopAnnotations();
        Set<AnnotationMirror> annotationSet = AnnotationUtils.createAnnotationSet();
        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 @GuardBy({}) on constructor
        // results.
        AnnotationMirror anno =
                constructorType
                        .getReturnType()
                        .getAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN);
        if (!AnnotationUtils.areSame(anno, atypeFactory.GUARDEDBY)) {
            super.checkConstructorResult(constructorType, constructorElement);
        }
    }

    @Override
    protected void commonAssignmentCheck(
            AnnotatedTypeMirror varType,
            AnnotatedTypeMirror valueType,
            Tree valueTree,
            @CompilerMessageKey String errorKey) {

        Kind valueTreeKind = valueTree.getKind();

        switch (valueTreeKind) {
            case NEW_CLASS:
            case NEW_ARRAY:
                // Avoid issuing warnings for: @GuardedBy(<something>) Object o = new Object();
                // Do NOT do this if the LHS is @GuardedByBottom.
                if (!varType.hasAnnotation(GuardedByBottom.class)) {
                    return;
                }
                break;
            case INT_LITERAL:
            case LONG_LITERAL:
            case FLOAT_LITERAL:
            case DOUBLE_LITERAL:
            case BOOLEAN_LITERAL:
            case CHAR_LITERAL:
            case STRING_LITERAL:
                // Avoid issuing warnings for: @GuardedBy(<something>) Object o; o = <some literal>;
                // Do NOT do this if the LHS is @GuardedByBottom.
                if (!varType.hasAnnotation(GuardedByBottom.class)) {
                    return;
                }
                break;
            default:
        }

        // 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.

        if (varType.hasAnnotation(GuardSatisfied.class)) {
            if (valueType.hasAnnotation(GuardedBy.class)) {
                checkLock(valueTree, valueType.getAnnotation(GuardedBy.class));
                return;
            } 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.report(
                                Result.failure(
                                        "guardsatisfied.assignment.disallowed", varType, valueType),
                                valueTree);
                    }
                } 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;
                }
            } 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;
                }
            }
        }

        super.commonAssignmentCheck(varType, valueType, valueTree, errorKey);
    }

    @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<String> overriderLocks,
            List<String> 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.report(
                    Result.failure(
                            messageKey, overriderMeth, overriderTyp, overriddenMeth, overriddenTyp),
                    overriderTree);
        } else {
            checker.report(
                    Result.failure(
                            messageKey,
                            overriderMeth,
                            overriderTyp,
                            overriddenMeth,
                            overriddenTyp,
                            overriderLocks,
                            overriddenLocks),
                    overriderTree);
        }
    }

    /**
     * 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;
    }

    @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.
     *
     * <p>{@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.
     *
     * <p>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 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(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")) {
                    // recvTree will be null for implicit this, or class name receivers. But they
                    // 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.

        ParameterizedExecutableType mType = atypeFactory.methodFromUse(node);
        AnnotatedExecutableType invokedMethod = mType.executableType;

        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[] =
                new int[requiredArgs.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(node);
            }
        }

        // Retrieve formal parameter types from the method definition

        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));
        }

        // 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 (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; // 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.report(
                                        Result.failure(
                                                "guardsatisfied.parameters.must.match",
                                                formalParam1,
                                                formalParam2,
                                                invokedMethod.toString(),
                                                guardSatisfiedIndex[i],
                                                arg1Anno,
                                                arg2Anno),
                                        node);
                            }
                        }
                    }
                }
            }
        }

        return super.visitMethodInvocation(node, 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, 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.
     *
     * <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);
    }

    /**
     * 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.
     *
     * <p>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
     */
    private void ensureExpressionIsEffectivelyFinal(final 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;

        while (true) {
            tree = TreeUtils.withoutParens(tree);

            switch (tree.getKind()) {
                case MEMBER_SELECT:
                    if (!isTreeSymbolEffectivelyFinalOrUnmodifiable(tree)) {
                        checker.report(
                                Result.failure("lock.expression.not.final", lockExpressionTree),
                                tree);
                        return;
                    }
                    tree = ((MemberSelectTree) tree).getExpression();
                    break;
                case IDENTIFIER:
                    if (!isTreeSymbolEffectivelyFinalOrUnmodifiable(tree)) {
                        checker.report(
                                Result.failure("lock.expression.not.final", lockExpressionTree),
                                tree);
                    }
                    return;
                case METHOD_INVOCATION:
                    Element elem = TreeUtils.elementFromUse(tree);
                    if (atypeFactory.getDeclAnnotationNoAliases(elem, Deterministic.class) == null
                            && atypeFactory.getDeclAnnotationNoAliases(elem, Pure.class) == null) {
                        checker.report(
                                Result.failure("lock.expression.not.final", lockExpressionTree),
                                tree);
                        return;
                    }

                    MethodInvocationTree methodInvocationTree = (MethodInvocationTree) tree;

                    for (ExpressionTree argTree : methodInvocationTree.getArguments()) {
                        ensureExpressionIsEffectivelyFinal(argTree);
                    }

                    tree = methodInvocationTree.getMethodSelect();
                    break;
                default:
                    checker.report(
                            Result.failure(
                                    "lock.expression.possibly.not.final", lockExpressionTree),
                            tree);
                    return;
            }
        }
    }

    private void ensureExpressionIsEffectivelyFinal(
            final Receiver lockExpr,
            String expressionForErrorReporting,
            Tree treeForErrorReporting) {
        if (!atypeFactory.isExpressionEffectivelyFinal(lockExpr)) {
            checker.report(
                    Result.failure("lock.expression.not.final", expressionForErrorReporting),
                    treeForErrorReporting);
        }
    }

    @Override
    public Void visitAnnotation(AnnotationTree tree, Void p) {
        ArrayList<AnnotationTree> annotationTreeList = new ArrayList<>(1);
        annotationTreeList.add(tree);
        List<AnnotationMirror> amList =
                TreeUtils.annotationsFromTypeAnnotationTrees(annotationTreeList);

        if (amList != null) {
            for (AnnotationMirror annotationMirror : amList) {
                if (AnnotationUtils.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 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.report(Result.failure("guardsatisfied.location.disallowed"), annotationTree);
    }

    /**
     * The flow expression 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.
     *
     * <p>Note: this is definitely necessary for local variable retrieval. It has not been tested
     * whether this is strictly necessary for fields or other identifiers.
     *
     * <p>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 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.
     */
    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
    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 accessed 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() != Kind.MEMBER_SELECT
                    || ((MemberSelectTree) parent).getExpression() == 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 node, Void p) {
        if (TreeUtils.isStringCompoundConcatenation(node)) {
            ExpressionTree rightTree = node.getExpression();
            if (!TypesUtils.isString(TreeUtils.typeOf(rightTree))) {
                checkPreconditionsForImplicitToStringCall(rightTree);
            }
        }

        return super.visitCompoundAssignment(node, 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().
     *
     * <p>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);
    }

    private void checkLock(Tree tree, AnnotationMirror gbAnno) {
        checkLockOfThisOrTree(tree, false, gbAnno);
    }

    private void checkLockOfThisOrTree(Tree tree, boolean implicitThis, AnnotationMirror gbAnno) {
        if (gbAnno == null) {
            throw new BugInCF("LockVisitor.checkLock: gbAnno cannot be null");
        }
        if (AnnotationUtils.areSameByClass(gbAnno, GuardedByUnknown.class)
                || AnnotationUtils.areSameByClass(gbAnno, GuardedByBottom.class)) {
            checker.report(Result.failure("lock.not.held", "unknown lock " + gbAnno), tree);
            return;
        } else if (AnnotationUtils.areSameByClass(gbAnno, GuardSatisfied.class)) {
            return;
        }

        List<LockExpression> expressions = getLockExpressions(implicitThis, gbAnno, tree);
        if (expressions.isEmpty()) {
            return;
        }

        LockStore store = atypeFactory.getStoreBefore(tree);
        for (LockExpression expression : expressions) {
            if (expression.error != null) {
                checker.report(
                        Result.failure(
                                "expression.unparsable.type.invalid", expression.error.toString()),
                        tree);
            } else if (expression.lockExpression == null) {
                checker.report(
                        Result.failure(
                                "expression.unparsable.type.invalid", expression.expressionString),
                        tree);
            } else if (!isLockHeld(expression.lockExpression, store)) {
                checker.report(
                        Result.failure("lock.not.held", expression.lockExpression.toString()),
                        tree);
            }

            if (expression.error != null && expression.lockExpression != null) {
                ensureExpressionIsEffectivelyFinal(
                        expression.lockExpression, expression.expressionString, tree);
            }
        }
    }

    private boolean isLockHeld(Receiver lock, LockStore store) {
        if (store == null) {
            return false;
        }
        CFAbstractValue<?> value = store.getValue(lock);
        if (value == null) {
            return false;
        }
        Set<AnnotationMirror> annos = value.getAnnotations();
        QualifierHierarchy hierarchy = atypeFactory.getQualifierHierarchy();
        AnnotationMirror lockAnno =
                hierarchy.findAnnotationInSameHierarchy(annos, atypeFactory.LOCKHELD);
        return lockAnno != null && AnnotationUtils.areSameByClass(lockAnno, LockHeld.class);
    }

    private List<LockExpression> getLockExpressions(
            boolean implicitThis, AnnotationMirror gbAnno, Tree tree) {

        List<String> expressions =
                AnnotationUtils.getElementValueArray(gbAnno, "value", String.class, true);

        if (expressions.isEmpty()) {
            return Collections.emptyList();
        }

        TreePath currentPath = getCurrentPath();
        List<Receiver> params =
                FlowExpressions.getParametersOfEnclosingMethod(atypeFactory, currentPath);

        TypeMirror enclosingType = TreeUtils.typeOf(TreeUtils.enclosingClass(currentPath));
        Receiver pseudoReceiver =
                FlowExpressions.internalReprOfPseudoReceiver(currentPath, enclosingType);
        FlowExpressionContext exprContext =
                new FlowExpressionContext(pseudoReceiver, params, atypeFactory.getContext());
        Receiver self;
        if (implicitThis) {
            self = pseudoReceiver;
        } else if (TreeUtils.isExpressionTree(tree)) {
            self = FlowExpressions.internalReprOf(atypeFactory, (ExpressionTree) tree);
        } else {
            self = new Unknown(TreeUtils.typeOf(tree));
        }

        List<LockExpression> lockExpressions = new ArrayList<>();
        for (String expression : expressions) {
            lockExpressions.add(parseExpressionString(expression, exprContext, currentPath, self));
        }
        return lockExpressions;
    }

    private LockExpression parseExpressionString(
            String expression,
            FlowExpressionContext flowExprContext,
            TreePath path,
            Receiver 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.report(
                                Result.failure(
                                        "lock.expression.not.final", lockExpression.lockExpression),
                                path.getLeaf());
                    }
                    return lockExpression;
                } else {
                    // TODO: The proper way to do this is to call
                    // flowExprContext.copyChangeToParsingMemberOfReceiver to set the receiver to
                    // the <self> expression, and then call FlowExpressionParseUtil.parse on the
                    // remaining expression string with the new flow expression context. However,
                    // this currently results in a FlowExpressions.Receiver that has a different
                    // hash code than if the following flow expression is parsed directly, which
                    // results in our inability to check that a lock expression is held as it does
                    // not match anything in the store due to the hash code mismatch.  For now,
                    // convert the "<self>" portion to the node's string representation, and parse
                    // the entire string:

                    lockExpression.lockExpression =
                            FlowExpressionParseUtil.parse(
                                    itself.toString() + "." + remainingExpression,
                                    flowExprContext,
                                    path,
                                    true);
                    if (!atypeFactory.isExpressionEffectivelyFinal(lockExpression.lockExpression)) {
                        checker.report(
                                Result.failure(
                                        "lock.expression.not.final", lockExpression.lockExpression),
                                path.getLeaf());
                    }
                    return lockExpression;
                }
            } else {
                lockExpression.lockExpression =
                        FlowExpressionParseUtil.parse(expression, flowExprContext, path, true);
                return lockExpression;
            }
        } catch (FlowExpressionParseException ex) {
            lockExpression.error = new DependentTypesError(expression, ex);
            return lockExpression;
        }
    }

    private static class LockExpression {
        final String expressionString;
        Receiver lockExpression = null;
        DependentTypesError error = null;

        LockExpression(String expression) {
            this.expressionString = expression;
        }
    }
}
