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

org.checkerframework.checker.lock.LockAnnotatedTypeFactory 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.44.0
Show newest version
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.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.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.NewObject;
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.TreeUtils;
import org.checkerframework.javacutil.TypeSystemError;
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.
 *
 * 

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 { /** 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 GuardedBy} annotation. */ protected final AnnotationMirror GUARDEDBY = createGuardedByAnnotationMirror(new ArrayList()); /** The @{@link NewObject} annotation. */ protected final AnnotationMirror NEWOBJECT = AnnotationBuilder.fromClass(elements, NewObject.class); /** 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 jcipGuardedBy; /** The javax.annotation.concurrent.GuardedBy annotation, or null if not on the classpath. */ protected final Class 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 classForNameOrNull(@ClassGetName String annotationClassName) { try { return (Class) Class.forName(annotationClassName); } catch (Exception e) { return null; } } @Override protected DependentTypesHelper createDependentTypesHelper() { return new DependentTypesHelper(this) { @Override protected void reportErrors(Tree errorTree, List errors) { // If the error message is NOT_EFFECTIVELY_FINAL, then report // lock.expression.not.final instead of expression.unparsable . List 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 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. * *

This method returns true in the following cases when expr is: * *

1. a field access and the field is final and the field access expression is effectively * final as specified by this method. * *

2. an effectively final local variable. * *

3. a deterministic method call whose arguments and receiver expression are effectively final * as specified by this method. * *

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> createSupportedTypeQualifiers() { return new LinkedHashSet<>( Arrays.asList( LockHeld.class, LockPossiblyHeld.class, GuardedBy.class, GuardedByUnknown.class, GuardSatisfied.class, NewObject.class, GuardedByBottom.class)); } @Override protected QualifierHierarchy createQualifierHierarchy() { return new LockQualifierHierarchy(getSupportedTypeQualifiers(), elements); } @Override protected LockAnalysis createFlowAnalysis() { return new LockAnalysis(checker, this); } @Override public LockTransfer createFlowTransferFunction( CFAbstractAnalysis analysis) { return new LockTransfer((LockAnalysis) analysis, (LockChecker) this.checker); } /** LockQualifierHierarchy. */ class LockQualifierHierarchy extends MostlyNoElementQualifierHierarchy { /** Qualifier kind for the @{@link GuardedByUnknown} annotation. */ private final QualifierKind GUARDEDBYUNKNOWN_KIND; /** 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 NewObject} annotation. */ private final QualifierKind NEWOBJECT_KIND; /** Qualifier kind for the @{@link GuardedByBottom} annotation. */ private final QualifierKind GUARDEDBYBOTTOM_KIND; /** * Creates a LockQualifierHierarchy. * * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy * @param elements element utils */ public LockQualifierHierarchy( Collection> qualifierClasses, Elements elements) { super(qualifierClasses, elements); GUARDEDBYUNKNOWN_KIND = getQualifierKind(GUARDEDBYUNKNOWN); GUARDEDBY_KIND = getQualifierKind(GUARDEDBY); GUARDSATISFIED_KIND = getQualifierKind(GUARDSATISFIED); NEWOBJECT_KIND = getQualifierKind(NEWOBJECT); GUARDEDBYBOTTOM_KIND = getQualifierKind(GUARDEDBYBOTTOM); } @Override protected boolean isSubtypeWithElements( AnnotationMirror subAnno, QualifierKind subKind, AnnotationMirror superAnno, QualifierKind superKind) { if (subKind == GUARDEDBY_KIND && superKind == GUARDEDBY_KIND) { List subLocks = AnnotationUtils.getElementValueArray( superAnno, guardedByValueElement, String.class, Collections.emptyList()); List 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 locks1 = AnnotationUtils.getElementValueArray( a1, guardedByValueElement, String.class, Collections.emptyList()); List 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; } else if (qualifierKind1 == NEWOBJECT_KIND) { return a2; } else if (qualifierKind2 == NEWOBJECT_KIND) { return a1; } throw new TypeSystemError( "leastUpperBoundWithElements(%s, %s, %s, %s, %s)", a1, qualifierKind1, a2, qualifierKind2, lubKind); } // GLB never returns @NewObject unless one of the argumetns is @NewObject; it returns // @GuardedByBottom instead, to prevent showing users the unexpected @NewObject type. @Override protected AnnotationMirror greatestLowerBoundWithElements( AnnotationMirror a1, QualifierKind qualifierKind1, AnnotationMirror a2, QualifierKind qualifierKind2, QualifierKind glbKind) { if (qualifierKind1 == GUARDEDBY_KIND && qualifierKind2 == GUARDEDBY_KIND) { List locks1 = AnnotationUtils.getElementValueArray( a1, guardedByValueElement, String.class, Collections.emptyList()); List 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 TypeSystemError( "greatestLowerBoundWithElements(%s, %s, %s, %s, %s)", a1, qualifierKind1, a2, qualifierKind2, glbKind); } } // 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 annotationClass; SideEffectAnnotation(String annotation, Class annotationClass) { this.annotation = annotation; this.annotationClass = annotationClass; } public String getNameOfSideEffectAnnotation() { return annotation; } public Class 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 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() != Tree.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 methodInvocationTreeArguments = ((MethodInvocationTree) tree).getArguments(); List paramTypes = AnnotatedTypes.adaptParameters(this, invokedMethod, methodInvocationTreeArguments); for (int i = 0; i < paramTypes.size(); i++) { if (replaceAnnotationInGuardedByHierarchyIfGuardSatisfiedIndexMatches( methodDefinitionReturn, paramTypes.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 valmap = anno.getElementValues(); Object value = null; for (ExecutableElement elem : valmap.keySet()) { if (elem.getSimpleName().contentEquals("value")) { value = valmap.get(elem).getValue(); break; } } List lockExpressions; if (value instanceof List) { @SuppressWarnings("unchecked") List la = (List) 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)); } } /** * Returns an AnnotationMirror corresponding to @GuardedBy(values). * * @param values a list of lock expressions * @return an AnnotationMirror corresponding to @GuardedBy(values) */ private AnnotationMirror createGuardedByAnnotationMirror(List values) { AnnotationBuilder builder = new AnnotationBuilder(getProcessingEnv(), GuardedBy.class); builder.setValue("value", values.toArray()); // Return the resulting AnnotationMirror return builder.build(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy