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

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

package org.checkerframework.checker.lock;

/*>>>
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.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.ClassName;
import org.checkerframework.dataflow.analysis.FlowExpressions.FieldAccess;
import org.checkerframework.dataflow.analysis.FlowExpressions.LocalVariable;
import org.checkerframework.dataflow.analysis.FlowExpressions.MethodCall;
import org.checkerframework.dataflow.analysis.FlowExpressions.Receiver;
import org.checkerframework.dataflow.analysis.FlowExpressions.ThisReference;
import org.checkerframework.dataflow.cfg.node.FieldAccessNode;
import org.checkerframework.dataflow.cfg.node.ImplicitThisLiteralNode;
import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
import org.checkerframework.dataflow.cfg.node.Node;
import org.checkerframework.dataflow.qual.Deterministic;
import org.checkerframework.dataflow.qual.Pure;
import org.checkerframework.dataflow.util.PurityUtils;
import org.checkerframework.framework.source.Result;
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.AnnotatedTypes;
import org.checkerframework.framework.util.FlowExpressionParseUtil;
import org.checkerframework.framework.util.FlowExpressionParseUtil.FlowExpressionContext;
import org.checkerframework.framework.util.FlowExpressionParseUtil.FlowExpressionParseException;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.ErrorReporter;
import org.checkerframework.javacutil.InternalUtils;
import org.checkerframework.javacutil.Pair;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypesUtils;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.LinkedHashSet;
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.TypeMirror;

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.ClassTree;
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;

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

    private static final Pattern itselfReceiverPattern = Pattern.compile("^itself(\\.(.*))?$");

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

        checkForAnnotatedJdk();
    }

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

        TypeMirror tm = InternalUtils.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("primitive.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:
* {@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 via checker.report. */ private void issueErrorIfMoreThanOneGuardedByAnnotationPresent(VariableTree variableTree) { int guardedByAnnotationCount = 0; List annos = InternalUtils .annotationsFromTypeAnnotationTrees(variableTree.getModifiers().getAnnotations()); for (AnnotationMirror anno : annos) { if (AnnotationUtils.areSameByClass(anno, GuardedBy.class) || AnnotationUtils.areSameByClass(anno, net.jcip.annotations.GuardedBy.class) || AnnotationUtils.areSameByClass(anno, javax.annotation.concurrent.GuardedBy.class)) { 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 this loop if it is already known that the warning must be issued. 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:
* {@code @Holding}
* {@code @net.jcip.annotations.GuardedBy}
* {@code @javax.annotation.concurrent.GuardedBy} * * @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++; } if (atypeFactory.getDeclAnnotation(methodElement, net.jcip.annotations.GuardedBy.class) != null) { lockPreconditionAnnotationCount++; } if (lockPreconditionAnnotationCount < 2 && atypeFactory.getDeclAnnotation(methodElement, javax.annotation.concurrent.GuardedBy.class) != null) { lockPreconditionAnnotationCount++; } 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 node 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 node, 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 annos = methodDefinitionReceiver.getAnnotations(); for (AnnotationMirror anno : annos) { if (AnnotationUtils.areSameByClass(anno, checkerGuardSatisfiedClass)) { MethodInvocationNode methodInvocationNode = (MethodInvocationNode) atypeFactory.getNodeForTree(node); Node receiverNode = methodInvocationNode.getTarget().getReceiver(); checkPreconditions(node, receiverNode, generatePreconditionsBasedOnGuards(methodCallReceiver)); return true; } } } return false; } @Override protected Set getExceptionParameterLowerBoundAnnotations() { Set tops = atypeFactory.getQualifierHierarchy().getTopAnnotations(); Set annotationSet = AnnotationUtils.createAnnotationSet(); for (AnnotationMirror anno : tops) { if (AnnotationUtils.areSame(anno, atypeFactory.GUARDEDBYUNKNOWN)) { annotationSet.add(atypeFactory.GUARDEDBY); } else { annotationSet.add(anno); } } return annotationSet; } /** * Given an AnnotatedTypeMirror containing a @GuardedBy annotation, returns the set of lock expression preconditions * specified in the @GuardedBy annotation. * Returns an empty set if no such expressions are found. * * @param atm the AnnotatedTypeMirror containing the @GuardedBy annotation with the lock expression preconditions. * @return a set of lock expression preconditions that can be processed by checkPreconditions */ private Set> generatePreconditionsBasedOnGuards(AnnotatedTypeMirror atm) { Set amList = atm.getAnnotations(); Set> preconditions = new LinkedHashSet<>(); if (amList != null) { for (AnnotationMirror annotationMirror : amList) { if (AnnotationUtils.areSameByClass(annotationMirror, checkerGuardedByClass)) { if (AnnotationUtils.hasElementValue(annotationMirror, "value")) { List guardedByValue = AnnotationUtils.getElementValueArray(annotationMirror, "value", String.class, false); for (String lockExpression : guardedByValue) { preconditions.add(Pair.of(lockExpression, LockHeld.class.getCanonicalName())); } } } } } return preconditions; } @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() 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() Object o; o = ; // 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)) { checkPreconditions((ExpressionTree) valueTree, generatePreconditionsBasedOnGuards(valueType)); 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 (atypeFactory.getNodeForTree(tree) instanceof FieldAccessNode) { Tree treeOfExpression = tree.getExpression(); Node nodeOfExpression = atypeFactory.getNodeForTree(treeOfExpression); checkFieldOrArrayAccess(tree, treeOfExpression, nodeOfExpression); } 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.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, Void p) { 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, p) && isValid; } /** * Checks that the field or array access is legal by checking that the locks * in the access's expression are held. * * @param accessTree field or array access tree to check (may be an identifier tree of a field) * @param treeToReportErrorAt tree whose location is used to report the error * @param expressionNode node of the field or array access's expression */ private void checkFieldOrArrayAccess(ExpressionTree accessTree, Tree treeToReportErrorAt, Node expressionNode) { AnnotatedTypeMirror atmOfReceiver = atypeFactory.getReceiverType(accessTree); if (treeToReportErrorAt != null && atmOfReceiver != null) { AnnotationMirror gb = atmOfReceiver.getEffectiveAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN); if (gb == null) { ErrorReporter.errorAbort("LockVisitor.checkFieldOrArrayAccess: gb cannot be null"); } if (AnnotationUtils.areSameByClass(gb, checkerGuardedByClass)) { Set> preconditions = generatePreconditionsBasedOnGuards(atmOfReceiver); checkPreconditions(treeToReportErrorAt, expressionNode, preconditions); } else if (AnnotationUtils.areSameByClass(gb, checkerGuardSatisfiedClass)) { // Can always dereference if type is @GuardSatisfied } else { // Can never dereference for any other types in the @GuardedBy hierarchy checker.report(Result.failure( "cannot.dereference", accessTree.toString(), AnnotationUtils.annotationSimpleName(gb)),accessTree); } } } @Override public Void visitArrayAccess(ArrayAccessTree tree, Void p) { Tree treeOfExpression = tree.getExpression(); Node nodeOfExpression = atypeFactory.getNodeForTree(treeOfExpression); checkFieldOrArrayAccess(tree, treeOfExpression, nodeOfExpression); 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 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 = TreeUtils.getReceiverTree(node); ensureReceiverOfExplicitUnlockCallIsEffectivelyFinal(node, methodElement, recvTree); // Handle acquiring of explicit locks. Verify that the lock expression is effectively final. // If the method causes expression "this" or "#1" to be locked, verify that those expressions are effectively final. // TODO: generalize to any expression. This is currently designed only to support methods in ReentrantLock // and ReentrantReadWriteLock (which use the "this" expression), as well as Thread.holdsLock (which uses // the "#1" expression). AnnotationMirror ensuresLockHeldAnno = atypeFactory.getDeclAnnotation(methodElement, EnsuresLockHeld.class); List 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. Pair> mfuPair = atypeFactory.methodFromUse(node); AnnotatedExecutableType invokedMethod = mfuPair.first; List 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 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 node the MethodInvocationTree for any method call * @param methodElement the ExecutableElement for the method call referred to by {@code node} * @param lockExpression the receiver tree of {@code node}. Can be null. */ private void ensureReceiverOfExplicitUnlockCallIsEffectivelyFinal(MethodInvocationTree node, 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 = InternalUtils.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(types, processingEnvironment.getElementUtils(), Lock.class); 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 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(types, processingEnvironment.getElementUtils(), Lock.class); 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. * * 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.skipParens(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) { // Keep the 'lockExpr' parameter intact for debugging purposes, and traverse the overall expression using 'expr' instead. Receiver expr = lockExpr; while (true) { if (expr instanceof FieldAccess) { FieldAccess fieldAccess = (FieldAccess) expr; Receiver recv = fieldAccess.getReceiver(); // Do NOT call fieldAccess.isUnmodifiableByOtherCode if the receiver is a method call, since it also checks if the receiver // is unmodifiable and does so incorrectly in that case. The present // method will determine whether or not a method call receiver is effectively final // (see the "if (expr instanceof MethodCall)" block below). if (!(fieldAccess.isUnmodifiableByOtherCode() || (fieldAccess.isFinal() && recv instanceof MethodCall))) { checker.report(Result.failure("lock.expression.not.final", expressionForErrorReporting), treeForErrorReporting); return; } expr = recv; } else if (expr instanceof LocalVariable) { if (!ElementUtils.isEffectivelyFinal(((LocalVariable) expr).getElement())) { checker.report(Result.failure("lock.expression.not.final", expressionForErrorReporting), treeForErrorReporting); } return; } else if (expr instanceof MethodCall) { MethodCall methodCall = (MethodCall) expr; for (Receiver param : methodCall.getParameters()) { ensureExpressionIsEffectivelyFinal(param, expressionForErrorReporting, treeForErrorReporting); } if (!PurityUtils.isDeterministic(atypeFactory, methodCall.getElement())) { checker.report(Result.failure("lock.expression.not.final", expressionForErrorReporting), treeForErrorReporting); return; } expr = methodCall.getReceiver(); } else if (expr instanceof ThisReference || // The current object is always final. expr instanceof ClassName) { // Class names are always final. // Neither ThisReference nor ClassName instances have a receiver, // so exit the loop. return; } else { // type of 'expr' is not supported in @GuardedBy(...) lock expressions checker.report(Result.failure("lock.expression.possibly.not.final", expressionForErrorReporting), treeForErrorReporting); return; } } } @Override public Void visitAnnotation(AnnotationTree tree, Void p) { ArrayList annotationTreeList = new ArrayList(1); annotationTreeList.add(tree); List amList = InternalUtils.annotationsFromTypeAnnotationTrees(annotationTreeList); if (amList != null) { for (AnnotationMirror annotationMirror : amList) { if (AnnotationUtils.areSameByClass(annotationMirror, checkerGuardedByClass)) { checkLockExpressionInGuardedByAnnotation(tree, annotationMirror); } else if (AnnotationUtils.areSameByClass(annotationMirror, checkerGuardSatisfiedClass)) { issueErrorIfGuardSatisfiedAnnotationInUnsupportedLocation(tree); } } } return super.visitAnnotation(tree, p); } /** * Check that the lock expression in a GuardedBy annotation is a valid flow expression * and is effectively final * @param tree AnnotationTree used for context and error reporting * @param guardedByAnnotation GuardedBy AnnotationMirror */ private void checkLockExpressionInGuardedByAnnotation(AnnotationTree tree, AnnotationMirror guardedByAnnotation) { List guardedByValue = AnnotationUtils.getElementValueArray(guardedByAnnotation, "value", String.class, true); if (guardedByValue.isEmpty()) { // getting the FlowExpressionContext could be costly, // so don't do it if there isn't a lock expression to check return; } TreePath path = getCurrentPath(); MethodTree enclMethod = TreeUtils.enclosingMethod(path); FlowExpressionContext flowExprContext; if (enclMethod != null) { flowExprContext = FlowExpressionParseUtil.buildFlowExprContextForDeclaration(enclMethod, path, checker.getContext()); } else { ClassTree enclosingClass = TreeUtils.enclosingClass(path); flowExprContext = FlowExpressionParseUtil.buildFlowExprContextForDeclaration(enclosingClass, path, checker.getContext()); } // Adapted from BaseTypeVisitor.checkPreconditions if (flowExprContext == null) { // The expressions cannot be parsed. Issue an error for the whole list of @GuardedBy expressions. checker.report(Result.failure("lock.expression.possibly.not.final", guardedByValue), tree); return; } TreePath pathForLocalVariableRetrieval = getPathForLocalVariableRetrieval(path); if (pathForLocalVariableRetrieval == null) { // The expressions cannot be parsed. Issue an error for the whole list of @GuardedBy expressions. checker.report(Result.failure("lock.expression.possibly.not.final", guardedByValue), tree); return; } for (String lockExpression : guardedByValue) { try { // Attempt to parse the lock expression. // This will also issue errors if the lock expressions are not final parseExpressionString(lockExpression, flowExprContext, pathForLocalVariableRetrieval, null, tree); } catch (FlowExpressionParseException e) { checker.report(e.getResult(), tree); } } } /** * Issues an error if a GuardSatisfied annotation is found in a location other than a method return type, receiver or parameter. * @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/515 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. * * 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 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 = InternalUtils.symbol(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) { Node node = atypeFactory.getNodeForTree(tree); if (node instanceof FieldAccessNode) { Node receiverNode = ((FieldAccessNode) node).getReceiver(); if (receiverNode instanceof ImplicitThisLiteralNode) { // All other field access are handle via visitMemberSelect checkFieldOrArrayAccess(tree, tree, receiverNode); } } return super.visitIdentifier(tree, p); } /** * If expression is "itself", and the flow expression parser cannot find a variable, * class, etc. named "itself", a flow expression receiver for {@code node} is returned, * unless {@code node} is null, in which case null is returned. * Also checks that the flow expression is effectively final and issues an error if it is not. *

* Returns the result of the super implementation otherwise. */ @Override protected FlowExpressions.Receiver parseExpressionString(String expression, FlowExpressionContext flowExprContext, TreePath path, Node node, Tree treeForErrorReporting) throws FlowExpressionParseException { FlowExpressions.Receiver expr = null; expression = expression.trim(); Matcher itselfReceiverMatcher = itselfReceiverPattern.matcher(expression); if (itselfReceiverMatcher.matches()) { expr = FlowExpressionParseUtil.parseAllowingItself(expression, flowExprContext, path); if (expr == null) { // No variable, class, etc. named "itself(.*)" could be found. // Hence "itself" is interpreted to actually mean itself. if (node == null) { // node is definitely null if this method was called by LockVisitor.visitAnnotation. // In this case, we skip the check to ensure that the "itself" expression is // effectively final at the site of the @GuardedBy("itself") annotation. return null; } String remainingExpression = itselfReceiverMatcher.group(2); if (remainingExpression == null || remainingExpression.isEmpty()) { expr = FlowExpressions.internalReprOf(atypeFactory, node); } else { // TODO: The proper way to do this is to call flowExprContext.changeReceiver to set the // receiver to the itself 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 "itself" portion to the node's string representation, and parse // the entire string: expr = FlowExpressionParseUtil.parse(node.toString() + "." + remainingExpression, flowExprContext, path); } } } else { expr = super.parseExpressionString(expression, flowExprContext, path, node, treeForErrorReporting); } ensureExpressionIsEffectivelyFinal(expr, expression, treeForErrorReporting); return expr; } /** * Disallows annotations from the @GuardedBy hierarchy on class declarations (other than @GuardedBy({}). */ @Override public Void visitClass(ClassTree node, Void p) { List annos = InternalUtils.annotationsFromTypeAnnotationTrees(node.getModifiers().getAnnotations()); for (AnnotationMirror anno : annos) { if (!AnnotationUtils.areSame(anno, atypeFactory.GUARDEDBY) && (AnnotationUtils.areSameIgnoringValues(anno, atypeFactory.GUARDEDBYUNKNOWN) || AnnotationUtils.areSameIgnoringValues(anno, atypeFactory.GUARDEDBY) || AnnotationUtils.areSameIgnoringValues(anno, atypeFactory.GUARDSATISFIED) || AnnotationUtils.areSameIgnoringValues(anno, atypeFactory.GUARDEDBYBOTTOM))) { checker.report(Result.failure("class.declaration.guardedby.annotation.invalid"), node); } } return super.visitClass(node, p); } @Override public Void visitBinary(BinaryTree node, Void p) { if (node.getKind() == Tree.Kind.PLUS) { Tree leftTree = node.getLeftOperand(); Tree rightTree = node.getRightOperand(); boolean lhsIsString = TypesUtils.isString(InternalUtils.typeOf(leftTree)); boolean rhsIsString = TypesUtils.isString(InternalUtils.typeOf(rightTree)); if (!lhsIsString && rhsIsString) { checkPreconditionsForImplicitToStringCall(leftTree); } else if (lhsIsString && !rhsIsString) { checkPreconditionsForImplicitToStringCall(rightTree); } } return super.visitBinary(node, p); } @Override public Void visitCompoundAssignment(CompoundAssignmentTree node, Void p) { if (node.getKind() == Tree.Kind.PLUS_ASSIGNMENT) { ExpressionTree rightTree = node.getExpression(); if (TypesUtils.isString(InternalUtils.typeOf(node.getVariable())) && !TypesUtils.isString(InternalUtils.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(). * * 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(Tree tree) { checkPreconditions(tree, generatePreconditionsBasedOnGuards(atypeFactory.getAnnotatedType(tree))); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy