package org.checkerframework.checker.lock; /*>>> import org.checkerframework.checker.interning.qual.*; */ 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 com.sun.source.util.TreePath; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; 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.VariableElement; 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.common.basetype.BaseTypeChecker; 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.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.source.Result; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; 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.AnnotationBuilder; import org.checkerframework.framework.util.FlowExpressionParseUtil; import org.checkerframework.framework.util.FlowExpressionParseUtil.FlowExpressionContext; import org.checkerframework.framework.util.MultiGraphQualifierHierarchy; import org.checkerframework.framework.util.MultiGraphQualifierHierarchy.MultiGraphFactory; import org.checkerframework.framework.util.dependenttypes.DependentTypesError; import org.checkerframework.framework.util.dependenttypes.DependentTypesHelper; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.InternalUtils; import org.checkerframework.javacutil.Pair; /** * 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"; /** Annotation constants */ protected final AnnotationMirror LOCKHELD, LOCKPOSSIBLYHELD, SIDEEFFECTFREE, GUARDEDBYUNKNOWN, GUARDEDBY, GUARDEDBYBOTTOM, GUARDSATISFIED; public LockAnnotatedTypeFactory(BaseTypeChecker checker) { super(checker, true); LOCKHELD = AnnotationUtils.fromClass(elements, LockHeld.class); LOCKPOSSIBLYHELD = AnnotationUtils.fromClass(elements, LockPossiblyHeld.class); SIDEEFFECTFREE = AnnotationUtils.fromClass(elements, SideEffectFree.class); GUARDEDBYUNKNOWN = AnnotationUtils.fromClass(elements, GuardedByUnknown.class); GUARDEDBY = AnnotationUtils.fromClass(elements, GuardedBy.class); GUARDEDBYBOTTOM = AnnotationUtils.fromClass(elements, GuardedByBottom.class); GUARDSATISFIED = AnnotationUtils.fromClass(elements, GuardSatisfied.class); // 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); postInit(); } @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 an expression.unparsable.type.invalid error. List<DependentTypesError> superErrors = new ArrayList<>(); for (DependentTypesError error : errors) { if (error.error.equals(NOT_EFFECTIVELY_FINAL)) { checker.report( Result.failure("lock.expression.not.final", error.expression), errorTree); } else { superErrors.add(error); } } super.reportErrors(errorTree, superErrors); } @Override protected String standardizeString( String expression, FlowExpressionContext context, TreePath localScope, boolean useLocalScope) { if (DependentTypesError.isExpressionError(expression)) { return expression; } // Adds logic to parse <self> expression, which only the Lock Checker uses. if (LockVisitor.selfReceiverPattern.matcher(expression).matches()) { return expression; } try { FlowExpressions.Receiver result = FlowExpressionParseUtil.parse( expression, context, localScope, useLocalScope); if (result == null) { return new DependentTypesError(expression, " ").toString(); } if (!isExpressionEffectivelyFinal(result)) { // If the expression isn't effectively final, then return the // NOT_EFFECTIVELY_FINAL error string. return new DependentTypesError(expression, NOT_EFFECTIVELY_FINAL) .toString(); } return result.toString(); } catch (FlowExpressionParseUtil.FlowExpressionParseException e) { return new DependentTypesError(expression, e).toString(); } } }; } /** * 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(Receiver expr) { if (expr instanceof FieldAccess) { FieldAccess fieldAccess = (FieldAccess) expr; Receiver recv = fieldAccess.getReceiver(); // Don't call fieldAccess return fieldAccess.isFinal() && isExpressionEffectivelyFinal(recv); } else if (expr instanceof LocalVariable) { return ElementUtils.isEffectivelyFinal(((LocalVariable) expr).getElement()); } else if (expr instanceof MethodCall) { MethodCall methodCall = (MethodCall) expr; for (Receiver param : methodCall.getParameters()) { if (!isExpressionEffectivelyFinal(param)) { 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<Class<? extends Annotation>>( Arrays.asList( LockHeld.class, LockPossiblyHeld.class, GuardedBy.class, GuardedByUnknown.class, GuardSatisfied.class, GuardedByBottom.class)); } @Override public QualifierHierarchy createQualifierHierarchy(MultiGraphFactory factory) { return new LockQualifierHierarchy(factory); } @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); } class LockQualifierHierarchy extends MultiGraphQualifierHierarchy { public LockQualifierHierarchy(MultiGraphFactory f) { super(f, LOCKHELD); } boolean isGuardedBy(AnnotationMirror am) { return AnnotationUtils.areSameIgnoringValues(am, GUARDEDBY); } boolean isGuardSatisfied(AnnotationMirror am) { return AnnotationUtils.areSameIgnoringValues(am, GUARDSATISFIED); } @Override public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) { boolean lhsIsGuardedBy = isGuardedBy(superAnno); boolean rhsIsGuardedBy = isGuardedBy(subAnno); if (lhsIsGuardedBy && rhsIsGuardedBy) { // Two @GuardedBy annotations are considered subtypes of each other if and only if their values match exactly. List<String> lhsValues = AnnotationUtils.getElementValueArray( superAnno, "value", String.class, true); List<String> rhsValues = AnnotationUtils.getElementValueArray(subAnno, "value", String.class, true); return rhsValues.containsAll(lhsValues) && lhsValues.containsAll(rhsValues); } boolean lhsIsGuardSatisfied = isGuardSatisfied(superAnno); boolean rhsIsGuardSatisfied = isGuardSatisfied(subAnno); if (lhsIsGuardSatisfied && rhsIsGuardSatisfied) { // There are cases in which two expressions with identical @GuardSatisfied(...) annotations are not // assignable. Those are handled elsewhere. // Two expressions with @GuardSatisfied annotations (without an index) are sometimes not assignable. // For example, two method actual parameters with @GuardSatisfied annotations are assumed to refer to different guards. // This is largely handled in methodFromUse and in LockVisitor.visitMethodInvocation. // Related behavior is handled in LockVisitor.visitMethod (issuing an error if a non-constructor method // definition has a return type of @GuardSatisfied without an index). // Two expressions with @GuardSatisfied() annotations are assignable when comparing a formal receiver // to an actual receiver (see LockVisitor.skipReceiverSubtypeCheck) or a formal parameter to an // actual parameter (see LockVisitor.commonAssignmentCheck for the details on this rule). return AnnotationUtils.areSame(superAnno, subAnno); } // Remove values from @GuardedBy annotations for further subtype checking. Remove indices from @GuardSatisfied annotations. if (lhsIsGuardedBy) { superAnno = GUARDEDBY; } else if (lhsIsGuardSatisfied) { superAnno = GUARDSATISFIED; } if (rhsIsGuardedBy) { subAnno = GUARDEDBY; } else if (rhsIsGuardSatisfied) { subAnno = GUARDSATISFIED; } return super.isSubtype(subAnno, superAnno); } @Override public AnnotationMirror greatestLowerBound(AnnotationMirror a1, AnnotationMirror a2) { AnnotationMirror a1top = getTopAnnotation(a1); AnnotationMirror a2top = getTopAnnotation(a2); if (AnnotationUtils.areSame(a1top, LOCKPOSSIBLYHELD) && AnnotationUtils.areSame(a2top, LOCKPOSSIBLYHELD)) { return greatestLowerBoundInLockPossiblyHeldHierarchy(a1, a2); } else if (AnnotationUtils.areSame(a1top, GUARDEDBYUNKNOWN) && AnnotationUtils.areSame(a2top, GUARDEDBYUNKNOWN)) { return greatestLowerBoundInGuardedByUnknownHierarchy(a1, a2); } return null; } private AnnotationMirror greatestLowerBoundInGuardedByUnknownHierarchy( AnnotationMirror a1, AnnotationMirror a2) { if (AnnotationUtils.areSame(a1, GUARDEDBYUNKNOWN)) { return a2; } if (AnnotationUtils.areSame(a2, GUARDEDBYUNKNOWN)) { return a1; } if ((isGuardedBy(a1) && isGuardedBy(a2)) || (isGuardSatisfied(a1) && isGuardSatisfied(a2))) { // isSubtype(a1, a2) is symmetrical to isSubtype(a2, a1) since two // @GuardedBy annotations are considered subtypes of each other // if and only if their values match exactly, and two @GuardSatisfied // annotations are considered subtypes of each other if and only if // their indices match exactly. if (isSubtype(a1, a2)) { return a1; } } return GUARDEDBYBOTTOM; } private AnnotationMirror greatestLowerBoundInLockPossiblyHeldHierarchy( AnnotationMirror a1, AnnotationMirror a2) { if (AnnotationUtils.areSame(a1, LOCKPOSSIBLYHELD)) { return a2; } if (AnnotationUtils.areSame(a2, LOCKPOSSIBLYHELD)) { return a1; } return LOCKHELD; } } // 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 * default for unchecked code. * * @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) { List<SideEffectAnnotation> sideEffectAnnotationPresent = new ArrayList<>(); for (SideEffectAnnotation sea : SideEffectAnnotation.values()) { if (getDeclAnnotationNoAliases(element, sea.getAnnotationClass()) != null) { sideEffectAnnotationPresent.add(sea); } } int count = sideEffectAnnotationPresent.size(); if (count == 0) { return defaults.applyUncheckedCodeDefaults(element) ? SideEffectAnnotation.MAYRELEASELOCKS : SideEffectAnnotation.RELEASESNOLOCKS; } if (count > 1 && issueErrorIfMoreThanOnePresent) { // TODO: Turn on after figuring out how this interacts with inherited annotations. // checker.report(Result.failure("multiple.sideeffect.annotations"), element); } SideEffectAnnotation weakest = sideEffectAnnotationPresent.get(0); // At least one side effect annotation was found. Return the weakest. for (SideEffectAnnotation sea : sideEffectAnnotationPresent) { if (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 on the GuardSatisfied annotation in the given AnnotatedTypeMirror. Assumes * atm is non-null and contains a GuardSatisfied annotation. * * @param atm 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 on the given GuardSatisfied annotation. Assumes am is non-null and is a * GuardSatisfied annotation. * * @param am AnnotationMirror for a GuardSatisfied annotation * @return the index on the GuardSatisfied annotation */ // package-private int getGuardSatisfiedIndex(AnnotationMirror am) { return AnnotationUtils.getElementValue(am, "value", Integer.class, true); } @Override public Pair<AnnotatedExecutableType, List<AnnotatedTypeMirror>> methodFromUse( ExpressionTree tree, ExecutableElement methodElt, AnnotatedTypeMirror receiverType) { Pair<AnnotatedExecutableType, List<AnnotatedTypeMirror>> mfuPair = super.methodFromUse(tree, methodElt, receiverType); if (tree.getKind() != Kind.METHOD_INVOCATION) { return mfuPair; } // 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 = mfuPair.first; if (invokedMethod.getElement().getKind() == ElementKind.CONSTRUCTOR) { return mfuPair; } AnnotatedTypeMirror methodDefinitionReturn = invokedMethod.getReturnType(); if (methodDefinitionReturn == null || !methodDefinitionReturn.hasAnnotation(GuardSatisfied.class)) { return mfuPair; } 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 mfuPair; } // 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 mfuPair; } 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 mfuPair; } } return mfuPair; } /** * 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 atmWithAnnotationInGuardedByHierarchy}. * * @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(InternalUtils.symbol((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 = getDeclAnnotation(element, net.jcip.annotations.GuardedBy.class); if (anno == null) { anno = getDeclAnnotation(element, javax.annotation.concurrent.GuardedBy.class); } if (anno == null) { return; } List<String> lockExpressions = AnnotationUtils.getElementValueArray(anno, "value", String.class, true); 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(); } }