| package org.checkerframework.checker.lock; |
| |
| import com.sun.source.tree.ExpressionTree; |
| import com.sun.source.tree.MethodInvocationTree; |
| import com.sun.source.tree.Tree; |
| import com.sun.source.tree.Tree.Kind; |
| import com.sun.source.tree.VariableTree; |
| import java.lang.annotation.Annotation; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.EnumSet; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import javax.lang.model.element.AnnotationMirror; |
| import javax.lang.model.element.AnnotationValue; |
| import javax.lang.model.element.Element; |
| import javax.lang.model.element.ElementKind; |
| import javax.lang.model.element.ExecutableElement; |
| import javax.lang.model.element.VariableElement; |
| import javax.lang.model.util.Elements; |
| import org.checkerframework.checker.lock.qual.EnsuresLockHeld; |
| import org.checkerframework.checker.lock.qual.EnsuresLockHeldIf; |
| import org.checkerframework.checker.lock.qual.GuardSatisfied; |
| import org.checkerframework.checker.lock.qual.GuardedBy; |
| import org.checkerframework.checker.lock.qual.GuardedByBottom; |
| import org.checkerframework.checker.lock.qual.GuardedByUnknown; |
| import org.checkerframework.checker.lock.qual.LockHeld; |
| import org.checkerframework.checker.lock.qual.LockPossiblyHeld; |
| import org.checkerframework.checker.lock.qual.LockingFree; |
| import org.checkerframework.checker.lock.qual.MayReleaseLocks; |
| import org.checkerframework.checker.lock.qual.ReleasesNoLocks; |
| import org.checkerframework.checker.nullness.qual.Nullable; |
| import org.checkerframework.checker.signature.qual.ClassGetName; |
| import org.checkerframework.common.basetype.BaseTypeChecker; |
| import org.checkerframework.dataflow.expression.ClassName; |
| import org.checkerframework.dataflow.expression.FieldAccess; |
| import org.checkerframework.dataflow.expression.JavaExpression; |
| import org.checkerframework.dataflow.expression.LocalVariable; |
| import org.checkerframework.dataflow.expression.MethodCall; |
| import org.checkerframework.dataflow.expression.ThisReference; |
| import org.checkerframework.dataflow.expression.Unknown; |
| import org.checkerframework.dataflow.qual.Pure; |
| import org.checkerframework.dataflow.qual.SideEffectFree; |
| import org.checkerframework.dataflow.util.PurityUtils; |
| import org.checkerframework.framework.flow.CFAbstractAnalysis; |
| import org.checkerframework.framework.flow.CFValue; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; |
| import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; |
| import org.checkerframework.framework.type.MostlyNoElementQualifierHierarchy; |
| import org.checkerframework.framework.type.QualifierHierarchy; |
| import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator; |
| import org.checkerframework.framework.type.treeannotator.TreeAnnotator; |
| import org.checkerframework.framework.util.AnnotatedTypes; |
| import org.checkerframework.framework.util.QualifierKind; |
| import org.checkerframework.framework.util.dependenttypes.DependentTypesError; |
| import org.checkerframework.framework.util.dependenttypes.DependentTypesHelper; |
| import org.checkerframework.javacutil.AnnotationBuilder; |
| import org.checkerframework.javacutil.AnnotationUtils; |
| import org.checkerframework.javacutil.ElementUtils; |
| import org.checkerframework.javacutil.Pair; |
| import org.checkerframework.javacutil.TreeUtils; |
| import org.plumelib.util.CollectionsPlume; |
| |
| /** |
| * LockAnnotatedTypeFactory builds types with @LockHeld and @LockPossiblyHeld annotations. LockHeld |
| * identifies that an object is being used as a lock and is being held when a given tree is |
| * executed. LockPossiblyHeld is the default type qualifier for this hierarchy and applies to all |
| * fields, local variables and parameters -- hence it does not convey any information other than |
| * that it is not LockHeld. |
| * |
| * <p>However, there are a number of other annotations used in conjunction with these annotations to |
| * enforce proper locking. |
| * |
| * @checker_framework.manual #lock-checker Lock Checker |
| */ |
| public class LockAnnotatedTypeFactory |
| extends GenericAnnotatedTypeFactory<CFValue, LockStore, LockTransfer, LockAnalysis> { |
| |
| /** dependent type annotation error message for when the expression is not effectively final. */ |
| public static final String NOT_EFFECTIVELY_FINAL = "lock expression is not effectively final"; |
| |
| /** The @{@link LockHeld} annotation. */ |
| protected final AnnotationMirror LOCKHELD = AnnotationBuilder.fromClass(elements, LockHeld.class); |
| /** The @{@link LockPossiblyHeld} annotation. */ |
| protected final AnnotationMirror LOCKPOSSIBLYHELD = |
| AnnotationBuilder.fromClass(elements, LockPossiblyHeld.class); |
| /** The @{@link SideEffectFree} annotation. */ |
| protected final AnnotationMirror SIDEEFFECTFREE = |
| AnnotationBuilder.fromClass(elements, SideEffectFree.class); |
| /** The @{@link GuardedByUnknown} annotation. */ |
| protected final AnnotationMirror GUARDEDBYUNKNOWN = |
| AnnotationBuilder.fromClass(elements, GuardedByUnknown.class); |
| /** The @{@link GuardedByBottom} annotation. */ |
| protected final AnnotationMirror GUARDEDBY = |
| createGuardedByAnnotationMirror(new ArrayList<String>()); |
| /** The @{@link GuardedByBottom} annotation. */ |
| protected final AnnotationMirror GUARDEDBYBOTTOM = |
| AnnotationBuilder.fromClass(elements, GuardedByBottom.class); |
| /** The @{@link GuardSatisfied} annotation. */ |
| protected final AnnotationMirror GUARDSATISFIED = |
| AnnotationBuilder.fromClass(elements, GuardSatisfied.class); |
| |
| /** The value() element/field of a @GuardedBy annotation. */ |
| protected final ExecutableElement guardedByValueElement = |
| TreeUtils.getMethod(GuardedBy.class, "value", 0, processingEnv); |
| /** The value() element/field of a @GuardSatisfied annotation. */ |
| protected final ExecutableElement guardSatisfiedValueElement = |
| TreeUtils.getMethod(GuardSatisfied.class, "value", 0, processingEnv); |
| /** The EnsuresLockHeld.value element/field. */ |
| protected final ExecutableElement ensuresLockHeldValueElement = |
| TreeUtils.getMethod(EnsuresLockHeld.class, "value", 0, processingEnv); |
| /** The EnsuresLockHeldIf.expression element/field. */ |
| protected final ExecutableElement ensuresLockHeldIfExpressionElement = |
| TreeUtils.getMethod(EnsuresLockHeldIf.class, "expression", 0, processingEnv); |
| |
| /** The net.jcip.annotations.GuardedBy annotation, or null if not on the classpath. */ |
| protected final Class<? extends Annotation> jcipGuardedBy; |
| |
| /** The javax.annotation.concurrent.GuardedBy annotation, or null if not on the classpath. */ |
| protected final Class<? extends Annotation> javaxGuardedBy; |
| |
| /** Create a new LockAnnotatedTypeFactory. */ |
| public LockAnnotatedTypeFactory(BaseTypeChecker checker) { |
| super(checker, true); |
| |
| // This alias is only true for the Lock Checker. All other checkers must |
| // ignore the @LockingFree annotation. |
| addAliasedDeclAnnotation(LockingFree.class, SideEffectFree.class, SIDEEFFECTFREE); |
| |
| // This alias is only true for the Lock Checker. All other checkers must |
| // ignore the @ReleasesNoLocks annotation. Note that ReleasesNoLocks is |
| // not truly side-effect-free even as far as the Lock Checker is concerned, |
| // so there is additional handling of this annotation in the Lock Checker. |
| addAliasedDeclAnnotation(ReleasesNoLocks.class, SideEffectFree.class, SIDEEFFECTFREE); |
| |
| jcipGuardedBy = classForNameOrNull("net.jcip.annotations.GuardedBy"); |
| |
| javaxGuardedBy = classForNameOrNull("javax.annotation.concurrent.GuardedBy"); |
| |
| postInit(); |
| } |
| |
| /** |
| * Returns the value of Class.forName, or null if Class.forName would throw an exception. |
| * |
| * @param annotationClassName an annotation's name, in ClassGetName format |
| * @return an annotation class or null |
| */ |
| @SuppressWarnings("unchecked") // cast to generic type |
| private Class<? extends Annotation> classForNameOrNull(@ClassGetName String annotationClassName) { |
| try { |
| return (Class<? extends Annotation>) Class.forName(annotationClassName); |
| } catch (Exception e) { |
| return null; |
| } |
| } |
| |
| @Override |
| protected DependentTypesHelper createDependentTypesHelper() { |
| return new DependentTypesHelper(this) { |
| @Override |
| protected void reportErrors(Tree errorTree, List<DependentTypesError> errors) { |
| // If the error message is NOT_EFFECTIVELY_FINAL, then report |
| // lock.expression.not.final instead of expression.unparsable . |
| List<DependentTypesError> superErrors = new ArrayList<>(errors.size()); |
| for (DependentTypesError error : errors) { |
| if (error.error.equals(NOT_EFFECTIVELY_FINAL)) { |
| checker.reportError(errorTree, "lock.expression.not.final", error.expression); |
| } else { |
| superErrors.add(error); |
| } |
| } |
| super.reportErrors(errorTree, superErrors); |
| } |
| |
| @Override |
| protected boolean shouldPassThroughExpression(String expression) { |
| // There is no expression to use to replace <self> here, so just pass the expression along. |
| return super.shouldPassThroughExpression(expression) |
| || LockVisitor.SELF_RECEIVER_PATTERN.matcher(expression).matches(); |
| } |
| |
| @Override |
| protected @Nullable JavaExpression transform(JavaExpression javaExpr) { |
| if (javaExpr instanceof Unknown || isExpressionEffectivelyFinal(javaExpr)) { |
| return javaExpr; |
| } |
| |
| // If the expression isn't effectively final, then return the NOT_EFFECTIVELY_FINAL error |
| // string. |
| return createError(javaExpr.toString(), NOT_EFFECTIVELY_FINAL); |
| } |
| }; |
| } |
| |
| /** |
| * Returns whether or not the expression is effectively final. |
| * |
| * <p>This method returns true in the following cases when expr is: |
| * |
| * <p>1. a field access and the field is final and the field access expression is effectively |
| * final as specified by this method. |
| * |
| * <p>2. an effectively final local variable. |
| * |
| * <p>3. a deterministic method call whose arguments and receiver expression are effectively final |
| * as specified by this method. |
| * |
| * <p>4. a this reference or a class literal |
| * |
| * @param expr expression |
| * @return whether or not the expression is effectively final |
| */ |
| boolean isExpressionEffectivelyFinal(JavaExpression expr) { |
| if (expr instanceof FieldAccess) { |
| FieldAccess fieldAccess = (FieldAccess) expr; |
| JavaExpression receiver = fieldAccess.getReceiver(); |
| // Don't call fieldAccess |
| return fieldAccess.isFinal() && isExpressionEffectivelyFinal(receiver); |
| } else if (expr instanceof LocalVariable) { |
| return ElementUtils.isEffectivelyFinal(((LocalVariable) expr).getElement()); |
| } else if (expr instanceof MethodCall) { |
| MethodCall methodCall = (MethodCall) expr; |
| for (JavaExpression arg : methodCall.getArguments()) { |
| if (!isExpressionEffectivelyFinal(arg)) { |
| return false; |
| } |
| } |
| return PurityUtils.isDeterministic(this, methodCall.getElement()) |
| && isExpressionEffectivelyFinal(methodCall.getReceiver()); |
| } else if (expr instanceof ThisReference || expr instanceof ClassName) { |
| // this is always final. "ClassName" is actually a class literal (String.class), it's final |
| // too. |
| return true; |
| } else { // type of 'expr' is not supported in @GuardedBy(...) lock expressions |
| return false; |
| } |
| } |
| |
| @Override |
| protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() { |
| return new LinkedHashSet<>( |
| Arrays.asList( |
| LockHeld.class, |
| LockPossiblyHeld.class, |
| GuardedBy.class, |
| GuardedByUnknown.class, |
| GuardSatisfied.class, |
| GuardedByBottom.class)); |
| } |
| |
| @Override |
| protected QualifierHierarchy createQualifierHierarchy() { |
| return new LockQualifierHierarchy(getSupportedTypeQualifiers(), elements); |
| } |
| |
| @Override |
| protected LockAnalysis createFlowAnalysis(List<Pair<VariableElement, CFValue>> fieldValues) { |
| return new LockAnalysis(checker, this, fieldValues); |
| } |
| |
| @Override |
| public LockTransfer createFlowTransferFunction( |
| CFAbstractAnalysis<CFValue, LockStore, LockTransfer> analysis) { |
| return new LockTransfer((LockAnalysis) analysis, (LockChecker) this.checker); |
| } |
| |
| /** LockQualifierHierarchy. */ |
| class LockQualifierHierarchy extends MostlyNoElementQualifierHierarchy { |
| |
| /** Qualifier kind for the @{@link GuardedBy} annotation. */ |
| private final QualifierKind GUARDEDBY_KIND; |
| /** Qualifier kind for the @{@link GuardSatisfied} annotation. */ |
| private final QualifierKind GUARDSATISFIED_KIND; |
| /** Qualifier kind for the @{@link GuardedByBottom} annotation. */ |
| private final QualifierKind GUARDEDBYBOTTOM_KIND; |
| /** Qualifier kind for the @{@link GuardedByUnknown} annotation. */ |
| private final QualifierKind GUARDEDBYUNKNOWN_KIND; |
| |
| /** |
| * Creates a LockQualifierHierarchy. |
| * |
| * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy |
| * @param elements element utils |
| */ |
| public LockQualifierHierarchy( |
| Collection<Class<? extends Annotation>> qualifierClasses, Elements elements) { |
| super(qualifierClasses, elements); |
| GUARDEDBY_KIND = getQualifierKind(GUARDEDBY); |
| GUARDSATISFIED_KIND = getQualifierKind(GUARDSATISFIED); |
| GUARDEDBYBOTTOM_KIND = getQualifierKind(GUARDEDBYBOTTOM); |
| GUARDEDBYUNKNOWN_KIND = getQualifierKind(GUARDEDBYUNKNOWN); |
| } |
| |
| @Override |
| protected boolean isSubtypeWithElements( |
| AnnotationMirror subAnno, |
| QualifierKind subKind, |
| AnnotationMirror superAnno, |
| QualifierKind superKind) { |
| if (subKind == GUARDEDBY_KIND && superKind == GUARDEDBY_KIND) { |
| List<String> subLocks = |
| AnnotationUtils.getElementValueArray( |
| superAnno, guardedByValueElement, String.class, Collections.emptyList()); |
| List<String> superLocks = |
| AnnotationUtils.getElementValueArray( |
| subAnno, guardedByValueElement, String.class, Collections.emptyList()); |
| return subLocks.containsAll(superLocks) && superLocks.containsAll(subLocks); |
| } else if (subKind == GUARDSATISFIED_KIND && superKind == GUARDSATISFIED_KIND) { |
| return AnnotationUtils.areSame(superAnno, subAnno); |
| } |
| throw new RuntimeException("Unexpected"); |
| } |
| |
| @Override |
| protected AnnotationMirror leastUpperBoundWithElements( |
| AnnotationMirror a1, |
| QualifierKind qualifierKind1, |
| AnnotationMirror a2, |
| QualifierKind qualifierKind2, |
| QualifierKind lubKind) { |
| if (qualifierKind1 == GUARDEDBY_KIND && qualifierKind2 == GUARDEDBY_KIND) { |
| List<String> locks1 = |
| AnnotationUtils.getElementValueArray( |
| a1, guardedByValueElement, String.class, Collections.emptyList()); |
| List<String> locks2 = |
| AnnotationUtils.getElementValueArray( |
| a2, guardedByValueElement, String.class, Collections.emptyList()); |
| if (locks1.containsAll(locks2) && locks2.containsAll(locks1)) { |
| return a1; |
| } else { |
| return GUARDEDBYUNKNOWN; |
| } |
| } else if (qualifierKind1 == GUARDSATISFIED_KIND && qualifierKind2 == GUARDSATISFIED_KIND) { |
| if (AnnotationUtils.areSame(a1, a2)) { |
| return a1; |
| } else { |
| return GUARDEDBYUNKNOWN; |
| } |
| } else if (qualifierKind1 == GUARDEDBYBOTTOM_KIND) { |
| return a2; |
| } else if (qualifierKind2 == GUARDEDBYBOTTOM_KIND) { |
| return a1; |
| } |
| throw new RuntimeException("Unexpected"); |
| } |
| |
| @Override |
| protected AnnotationMirror greatestLowerBoundWithElements( |
| AnnotationMirror a1, |
| QualifierKind qualifierKind1, |
| AnnotationMirror a2, |
| QualifierKind qualifierKind2, |
| QualifierKind glbKind) { |
| if (qualifierKind1 == GUARDEDBY_KIND && qualifierKind2 == GUARDEDBY_KIND) { |
| List<String> locks1 = |
| AnnotationUtils.getElementValueArray( |
| a1, guardedByValueElement, String.class, Collections.emptyList()); |
| List<String> locks2 = |
| AnnotationUtils.getElementValueArray( |
| a2, guardedByValueElement, String.class, Collections.emptyList()); |
| if (locks1.containsAll(locks2) && locks2.containsAll(locks1)) { |
| return a1; |
| } else { |
| return GUARDEDBYBOTTOM; |
| } |
| } else if (qualifierKind1 == GUARDSATISFIED_KIND && qualifierKind2 == GUARDSATISFIED_KIND) { |
| if (AnnotationUtils.areSame(a1, a2)) { |
| return a1; |
| } else { |
| return GUARDEDBYBOTTOM; |
| } |
| } else if (qualifierKind1 == GUARDEDBYUNKNOWN_KIND) { |
| return a2; |
| } else if (qualifierKind2 == GUARDEDBYUNKNOWN_KIND) { |
| return a1; |
| } |
| throw new RuntimeException("Unexpected"); |
| } |
| } |
| |
| // The side effect annotations processed by the Lock Checker. |
| enum SideEffectAnnotation { |
| MAYRELEASELOCKS("@MayReleaseLocks", MayReleaseLocks.class), |
| RELEASESNOLOCKS("@ReleasesNoLocks", ReleasesNoLocks.class), |
| LOCKINGFREE("@LockingFree", LockingFree.class), |
| SIDEEFFECTFREE("@SideEffectFree", SideEffectFree.class), |
| PURE("@Pure", Pure.class); |
| final String annotation; |
| final Class<? extends Annotation> annotationClass; |
| |
| SideEffectAnnotation(String annotation, Class<? extends Annotation> annotationClass) { |
| this.annotation = annotation; |
| this.annotationClass = annotationClass; |
| } |
| |
| public String getNameOfSideEffectAnnotation() { |
| return annotation; |
| } |
| |
| public Class<? extends Annotation> getAnnotationClass() { |
| return annotationClass; |
| } |
| |
| /** |
| * Returns true if the receiver side effect annotation is weaker than side effect annotation |
| * 'other'. |
| */ |
| boolean isWeakerThan(SideEffectAnnotation other) { |
| boolean weaker = false; |
| |
| switch (other) { |
| case MAYRELEASELOCKS: |
| break; |
| case RELEASESNOLOCKS: |
| if (this == SideEffectAnnotation.MAYRELEASELOCKS) { |
| weaker = true; |
| } |
| break; |
| case LOCKINGFREE: |
| switch (this) { |
| case MAYRELEASELOCKS: |
| case RELEASESNOLOCKS: |
| weaker = true; |
| break; |
| default: |
| } |
| break; |
| case SIDEEFFECTFREE: |
| switch (this) { |
| case MAYRELEASELOCKS: |
| case RELEASESNOLOCKS: |
| case LOCKINGFREE: |
| weaker = true; |
| break; |
| default: |
| } |
| break; |
| case PURE: |
| switch (this) { |
| case MAYRELEASELOCKS: |
| case RELEASESNOLOCKS: |
| case LOCKINGFREE: |
| case SIDEEFFECTFREE: |
| weaker = true; |
| break; |
| default: |
| } |
| break; |
| } |
| |
| return weaker; |
| } |
| |
| static SideEffectAnnotation weakest = null; |
| |
| public static SideEffectAnnotation weakest() { |
| if (weakest == null) { |
| for (SideEffectAnnotation sea : SideEffectAnnotation.values()) { |
| if (weakest == null) { |
| weakest = sea; |
| } |
| if (sea.isWeakerThan(weakest)) { |
| weakest = sea; |
| } |
| } |
| } |
| return weakest; |
| } |
| } |
| |
| /** |
| * Indicates which side effect annotation is present on the given method. If more than one |
| * annotation is present, this method issues an error (if issueErrorIfMoreThanOnePresent is true) |
| * and returns the annotation providing the weakest guarantee. Only call with |
| * issueErrorIfMoreThanOnePresent == true when visiting a method definition. This prevents |
| * multiple errors being issued for the same method (as would occur if |
| * issueErrorIfMoreThanOnePresent were set to true when visiting method invocations). If no |
| * annotation is present, return RELEASESNOLOCKS as the default, and MAYRELEASELOCKS as the |
| * conservative default. |
| * |
| * @param element the method element |
| * @param issueErrorIfMoreThanOnePresent whether to issue an error if more than one side effect |
| * annotation is present on the method |
| */ |
| // package-private |
| SideEffectAnnotation methodSideEffectAnnotation( |
| Element element, boolean issueErrorIfMoreThanOnePresent) { |
| if (element != null) { |
| Set<SideEffectAnnotation> sideEffectAnnotationPresent = |
| EnumSet.noneOf(SideEffectAnnotation.class); |
| for (SideEffectAnnotation sea : SideEffectAnnotation.values()) { |
| if (getDeclAnnotationNoAliases(element, sea.getAnnotationClass()) != null) { |
| sideEffectAnnotationPresent.add(sea); |
| } |
| } |
| |
| int count = sideEffectAnnotationPresent.size(); |
| |
| if (count == 0) { |
| return defaults.applyConservativeDefaults(element) |
| ? SideEffectAnnotation.MAYRELEASELOCKS |
| : SideEffectAnnotation.RELEASESNOLOCKS; |
| } |
| |
| if (count > 1 && issueErrorIfMoreThanOnePresent) { |
| // TODO: Turn on after figuring out how this interacts with inherited annotations. |
| // checker.reportError(element, "multiple.sideeffect.annotations"); |
| } |
| |
| SideEffectAnnotation weakest = null; |
| // At least one side effect annotation was found. Return the weakest. |
| for (SideEffectAnnotation sea : sideEffectAnnotationPresent) { |
| if (weakest == null || sea.isWeakerThan(weakest)) { |
| weakest = sea; |
| } |
| } |
| return weakest; |
| } |
| |
| // When there is not enough information to determine the correct side effect annotation, |
| // return the weakest one. |
| return SideEffectAnnotation.weakest(); |
| } |
| |
| /** |
| * Returns the index (that is, the {@code value} element) on the {@code @GuardSatisfied} |
| * annotation in the given AnnotatedTypeMirror. Assumes atm is non-null and contains a |
| * {@code @GuardSatisfied} annotation. |
| * |
| * @param atm an AnnotatedTypeMirror containing a GuardSatisfied annotation |
| * @return the index on the GuardSatisfied annotation |
| */ |
| // package-private |
| int getGuardSatisfiedIndex(AnnotatedTypeMirror atm) { |
| return getGuardSatisfiedIndex(atm.getAnnotation(GuardSatisfied.class)); |
| } |
| |
| /** |
| * Returns the index (that is, the {@code value} element) on the given {@code @GuardSatisfied} |
| * annotation. Assumes am is non-null and is a GuardSatisfied annotation. |
| * |
| * @param am an AnnotationMirror for a GuardSatisfied annotation |
| * @return the index on the GuardSatisfied annotation |
| */ |
| // package-private |
| int getGuardSatisfiedIndex(AnnotationMirror am) { |
| return AnnotationUtils.getElementValueInt(am, guardSatisfiedValueElement, -1); |
| } |
| |
| @Override |
| public ParameterizedExecutableType methodFromUse( |
| ExpressionTree tree, ExecutableElement methodElt, AnnotatedTypeMirror receiverType) { |
| ParameterizedExecutableType mType = super.methodFromUse(tree, methodElt, receiverType); |
| |
| if (tree.getKind() != Kind.METHOD_INVOCATION) { |
| return mType; |
| } |
| |
| // If a method's formal return type is annotated with @GuardSatisfied(index), look for the |
| // first instance of @GuardSatisfied(index) in the method definition's receiver type or |
| // formal parameters, retrieve the corresponding type of the actual parameter / receiver at |
| // the call site (e.g. @GuardedBy("someLock") and replace the return type at the call site |
| // with this type. |
| |
| AnnotatedExecutableType invokedMethod = mType.executableType; |
| |
| if (invokedMethod.getElement().getKind() == ElementKind.CONSTRUCTOR) { |
| return mType; |
| } |
| |
| AnnotatedTypeMirror methodDefinitionReturn = invokedMethod.getReturnType(); |
| |
| if (methodDefinitionReturn == null |
| || !methodDefinitionReturn.hasAnnotation(GuardSatisfied.class)) { |
| return mType; |
| } |
| |
| int returnGuardSatisfiedIndex = getGuardSatisfiedIndex(methodDefinitionReturn); |
| |
| // @GuardSatisfied with no index defaults to index -1. Ignore instances of @GuardSatisfied |
| // with no index. If a method is defined with a return type of @GuardSatisfied with no |
| // index, an error is reported by LockVisitor.visitMethod. |
| |
| if (returnGuardSatisfiedIndex == -1) { |
| return mType; |
| } |
| |
| // Find the receiver or first parameter whose @GS index matches that of the return type. |
| // Ensuring that the type annotations on distinct @GS parameters with the same index |
| // match at the call site is handled in LockVisitor.visitMethodInvocation |
| |
| if (!ElementUtils.isStatic(invokedMethod.getElement()) |
| && replaceAnnotationInGuardedByHierarchyIfGuardSatisfiedIndexMatches( |
| methodDefinitionReturn, |
| invokedMethod.getReceiverType() /* the method definition receiver*/, |
| returnGuardSatisfiedIndex, |
| receiverType.getAnnotationInHierarchy(GUARDEDBYUNKNOWN))) { |
| return mType; |
| } |
| |
| List<? extends ExpressionTree> methodInvocationTreeArguments = |
| ((MethodInvocationTree) tree).getArguments(); |
| List<AnnotatedTypeMirror> requiredArgs = |
| AnnotatedTypes.expandVarArgs(this, invokedMethod, methodInvocationTreeArguments); |
| |
| for (int i = 0; i < requiredArgs.size(); i++) { |
| if (replaceAnnotationInGuardedByHierarchyIfGuardSatisfiedIndexMatches( |
| methodDefinitionReturn, |
| requiredArgs.get(i), |
| returnGuardSatisfiedIndex, |
| getAnnotatedType(methodInvocationTreeArguments.get(i)) |
| .getEffectiveAnnotationInHierarchy(GUARDEDBYUNKNOWN))) { |
| return mType; |
| } |
| } |
| |
| return mType; |
| } |
| |
| /** |
| * If {@code atm} is not null and contains a {@code @GuardSatisfied} annotation, and if the index |
| * of this {@code @GuardSatisfied} annotation matches {@code matchingGuardSatisfiedIndex}, then |
| * {@code methodReturnAtm} will have its annotation in the {@code @GuardedBy} hierarchy replaced |
| * with that in {@code annotationInGuardedByHierarchy}. |
| * |
| * @param methodReturnAtm the AnnotatedTypeMirror for the return type of a method that will |
| * potentially have its annotation in the {@code @GuardedBy} hierarchy replaced. |
| * @param atm an AnnotatedTypeMirror that may contain a {@code @GuardSatisfied} annotation. May be |
| * null. |
| * @param matchingGuardSatisfiedIndex the {code @GuardSatisfied} index that the |
| * {@code @GuardSatisfied} annotation in {@code atm} must have in order for the replacement to |
| * occur. |
| * @param annotationInGuardedByHierarchy if the replacement occurs, the annotation in the |
| * {@code @GuardedBy} hierarchy in this parameter will be used for the replacement. |
| * @return true if the replacement occurred, false otherwise |
| */ |
| private boolean replaceAnnotationInGuardedByHierarchyIfGuardSatisfiedIndexMatches( |
| AnnotatedTypeMirror methodReturnAtm, |
| AnnotatedTypeMirror atm, |
| int matchingGuardSatisfiedIndex, |
| AnnotationMirror annotationInGuardedByHierarchy) { |
| if (atm == null |
| || !atm.hasAnnotation(GuardSatisfied.class) |
| || getGuardSatisfiedIndex(atm) != matchingGuardSatisfiedIndex) { |
| return false; |
| } |
| |
| methodReturnAtm.replaceAnnotation(annotationInGuardedByHierarchy); |
| |
| return true; |
| } |
| |
| @Override |
| protected TreeAnnotator createTreeAnnotator() { |
| return new ListTreeAnnotator(new LockTreeAnnotator(this), super.createTreeAnnotator()); |
| } |
| |
| @Override |
| public void addComputedTypeAnnotations(Element elt, AnnotatedTypeMirror type) { |
| translateJcipAndJavaxAnnotations(elt, type); |
| |
| super.addComputedTypeAnnotations(elt, type); |
| } |
| |
| @Override |
| public void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type, boolean useFlow) { |
| if (tree.getKind() == Tree.Kind.VARIABLE) { |
| translateJcipAndJavaxAnnotations(TreeUtils.elementFromTree((VariableTree) tree), type); |
| } |
| |
| super.addComputedTypeAnnotations(tree, type, useFlow); |
| } |
| |
| /** |
| * Given a field declaration with a {@code @net.jcip.annotations.GuardedBy} or {@code |
| * javax.annotation.concurrent.GuardedBy} annotation and an AnnotatedTypeMirror for that field, |
| * inserts the corresponding {@code @org.checkerframework.checker.lock.qual.GuardedBy} type |
| * qualifier into that AnnotatedTypeMirror. |
| * |
| * @param element any Element (this method does nothing if the Element is not for a field |
| * declaration) |
| * @param atm the AnnotatedTypeMirror for element - the {@code @GuardedBy} type qualifier will be |
| * inserted here |
| */ |
| private void translateJcipAndJavaxAnnotations(Element element, AnnotatedTypeMirror atm) { |
| if (!element.getKind().isField()) { |
| return; |
| } |
| |
| AnnotationMirror anno = null; |
| |
| if (jcipGuardedBy != null) { |
| anno = getDeclAnnotation(element, jcipGuardedBy); |
| } |
| |
| if (anno == null && javaxGuardedBy != null) { |
| anno = getDeclAnnotation(element, javaxGuardedBy); |
| } |
| |
| if (anno == null) { |
| return; |
| } |
| |
| // The version of javax.annotation.concurrent.GuardedBy included with the Checker Framework |
| // declares the type of value as an array of Strings, whereas the one defined in JCIP and |
| // included with FindBugs declares it as a String. So, the code below figures out which type |
| // should be used. |
| Map<? extends ExecutableElement, ? extends AnnotationValue> valmap = anno.getElementValues(); |
| Object value = null; |
| for (ExecutableElement elem : valmap.keySet()) { |
| if (elem.getSimpleName().contentEquals("value")) { |
| value = valmap.get(elem).getValue(); |
| break; |
| } |
| } |
| List<String> lockExpressions; |
| if (value instanceof List) { |
| @SuppressWarnings("unchecked") |
| List<AnnotationValue> la = (List<AnnotationValue>) value; |
| lockExpressions = CollectionsPlume.mapList((AnnotationValue a) -> (String) a.getValue(), la); |
| } else if (value instanceof String) { |
| lockExpressions = Collections.singletonList((String) value); |
| } else { |
| return; |
| } |
| |
| if (lockExpressions.isEmpty()) { |
| atm.addAnnotation(GUARDEDBY); |
| } else { |
| atm.addAnnotation(createGuardedByAnnotationMirror(lockExpressions)); |
| } |
| } |
| |
| /** |
| * @param values a list of lock expressions |
| * @return an AnnotationMirror corresponding to @GuardedBy(values) |
| */ |
| private AnnotationMirror createGuardedByAnnotationMirror(List<String> values) { |
| AnnotationBuilder builder = new AnnotationBuilder(getProcessingEnv(), GuardedBy.class); |
| builder.setValue("value", values.toArray()); |
| |
| // Return the resulting AnnotationMirror |
| return builder.build(); |
| } |
| } |