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

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

Go to download

The Checker Framework enhances Java’s type system to make it more powerful and useful. This lets software developers detect and prevent errors in their Java programs. The Checker Framework includes compiler plug-ins ("checkers") that find bugs or verify their absence. It also permits you to write your own compiler plug-ins.

There is a newer version: 3.42.0
Show newest version
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