package org.checkerframework.checker.index.upperbound; import com.sun.source.tree.BinaryTree; import com.sun.source.tree.CompoundAssignmentTree; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.Tree; import com.sun.source.tree.Tree.Kind; import com.sun.source.tree.UnaryTree; import com.sun.source.util.TreePath; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; 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 org.checkerframework.checker.index.IndexMethodIdentifier; import org.checkerframework.checker.index.IndexUtil; import org.checkerframework.checker.index.lowerbound.LowerBoundAnnotatedTypeFactory; import org.checkerframework.checker.index.lowerbound.LowerBoundChecker; import org.checkerframework.checker.index.qual.IndexFor; import org.checkerframework.checker.index.qual.IndexOrHigh; import org.checkerframework.checker.index.qual.IndexOrLow; import org.checkerframework.checker.index.qual.LTEqLengthOf; import org.checkerframework.checker.index.qual.LTLengthOf; import org.checkerframework.checker.index.qual.LTOMLengthOf; import org.checkerframework.checker.index.qual.LengthOf; import org.checkerframework.checker.index.qual.NegativeIndexFor; import org.checkerframework.checker.index.qual.NonNegative; import org.checkerframework.checker.index.qual.PolyIndex; import org.checkerframework.checker.index.qual.PolyUpperBound; import org.checkerframework.checker.index.qual.Positive; import org.checkerframework.checker.index.qual.SameLen; import org.checkerframework.checker.index.qual.SearchIndexFor; import org.checkerframework.checker.index.qual.UpperBoundBottom; import org.checkerframework.checker.index.qual.UpperBoundUnknown; import org.checkerframework.checker.index.samelen.SameLenAnnotatedTypeFactory; import org.checkerframework.checker.index.samelen.SameLenChecker; import org.checkerframework.checker.index.searchindex.SearchIndexAnnotatedTypeFactory; import org.checkerframework.checker.index.searchindex.SearchIndexChecker; import org.checkerframework.checker.index.upperbound.UBQualifier.LessThanLengthOf; import org.checkerframework.checker.index.upperbound.UBQualifier.UpperBoundUnknownQualifier; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.value.ValueAnnotatedTypeFactory; import org.checkerframework.common.value.ValueChecker; import org.checkerframework.common.value.qual.BottomVal; import org.checkerframework.dataflow.cfg.node.Node; import org.checkerframework.framework.qual.PolyAll; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; 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.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.framework.util.dependenttypes.DependentTypesTreeAnnotator; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.TreeUtils; /** * Implements the introduction rules for the Upper Bound Checker. Works primarily by way of querying * the MinLen Checker and comparing the min lengths of arrays to the known values of variables as * supplied by the Value Checker. */ public class UpperBoundAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { public final AnnotationMirror UNKNOWN, BOTTOM, POLY; private final IndexMethodIdentifier imf; public UpperBoundAnnotatedTypeFactory(BaseTypeChecker checker) { super(checker); UNKNOWN = AnnotationUtils.fromClass(elements, UpperBoundUnknown.class); BOTTOM = AnnotationUtils.fromClass(elements, UpperBoundBottom.class); POLY = AnnotationUtils.fromClass(elements, PolyUpperBound.class); addAliasedAnnotation(IndexFor.class, createLTLengthOfAnnotation()); addAliasedAnnotation(IndexOrLow.class, createLTLengthOfAnnotation()); addAliasedAnnotation(IndexOrHigh.class, createLTEqLengthOfAnnotation()); addAliasedAnnotation(SearchIndexFor.class, createLTLengthOfAnnotation()); addAliasedAnnotation(NegativeIndexFor.class, createLTLengthOfAnnotation()); addAliasedAnnotation(LengthOf.class, createLTEqLengthOfAnnotation()); addAliasedAnnotation(PolyAll.class, POLY); addAliasedAnnotation(PolyIndex.class, POLY); imf = new IndexMethodIdentifier(processingEnv); this.postInit(); } @Override protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() { // Because the Index Checker is a subclass, the qualifiers have to be explicitly defined. return new LinkedHashSet<>( Arrays.asList( UpperBoundUnknown.class, LTEqLengthOf.class, LTLengthOf.class, LTOMLengthOf.class, UpperBoundBottom.class, PolyUpperBound.class)); } /** * Provides a way to query the Constant Value Checker, which computes the values of expressions * known at compile time (constant propagation and folding). */ ValueAnnotatedTypeFactory getValueAnnotatedTypeFactory() { return getTypeFactoryOfSubchecker(ValueChecker.class); } /** * Provides a way to query the Search Index Checker, which helps the Index Checker type the * results of calling the JDK's binary search methods correctly. */ private SearchIndexAnnotatedTypeFactory getSearchIndexAnnotatedTypeFactory() { return getTypeFactoryOfSubchecker(SearchIndexChecker.class); } /** * Provides a way to query the SameLen (same length) Checker, which determines the relationships * among the lengths of arrays. */ SameLenAnnotatedTypeFactory getSameLenAnnotatedTypeFactory() { return getTypeFactoryOfSubchecker(SameLenChecker.class); } /** * Provides a way to query the Lower Bound Checker, which determines whether each integer in the * program is non-negative or not, and checks that no possibly negative integers are used to * access arrays. */ private LowerBoundAnnotatedTypeFactory getLowerBoundAnnotatedTypeFactory() { return getTypeFactoryOfSubchecker(LowerBoundChecker.class); } @Override public void addComputedTypeAnnotations(Element element, AnnotatedTypeMirror type) { super.addComputedTypeAnnotations(element, type); if (element != null) { AnnotatedTypeMirror valueType = getValueAnnotatedTypeFactory().getAnnotatedType(element); addUpperBoundTypeFromValueType(valueType, type); } } @Override public void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type, boolean iUseFlow) { super.addComputedTypeAnnotations(tree, type, iUseFlow); // If dataflow shouldn't be used to compute this type, then do not use the result from // the Value Checker, because dataflow is used to compute that type. (Without this, // "int i = 1; --i;" fails.) if (iUseFlow && tree != null && TreeUtils.isExpressionTree(tree)) { AnnotatedTypeMirror valueType = getValueAnnotatedTypeFactory().getAnnotatedType(tree); addUpperBoundTypeFromValueType(valueType, type); } } /** * Checks if valueType contains a {@link org.checkerframework.common.value.qual.BottomVal} * annotation. If so, adds an {@link UpperBoundBottom} annotation to type. */ private void addUpperBoundTypeFromValueType( AnnotatedTypeMirror valueType, AnnotatedTypeMirror type) { if (AnnotationUtils.containsSameByClass(valueType.getAnnotations(), BottomVal.class)) { type.replaceAnnotation(BOTTOM); } } @Override protected DependentTypesHelper createDependentTypesHelper() { return new DependentTypesHelper(this) { @Override protected String standardizeString( final String expression, FlowExpressionContext context, TreePath localScope, boolean useLocalScope) { if (DependentTypesError.isExpressionError(expression)) { return expression; } if (indexOf(expression, '-', '+', 0) == -1) { return super.standardizeString(expression, context, localScope, useLocalScope); } OffsetEquation equation = OffsetEquation.createOffsetFromJavaExpression(expression); if (equation.hasError()) { return equation.getError(); } try { equation.standardizeAndViewpointAdaptExpressions( context, localScope, useLocalScope); } catch (FlowExpressionParseUtil.FlowExpressionParseException e) { return new DependentTypesError(expression, e).toString(); } return equation.toString(); } private int indexOf(String expression, char a, char b, int index) { int aIndex = expression.indexOf(a, index); int bIndex = expression.indexOf(b, index); if (aIndex == -1) { return bIndex; } else if (bIndex == -1) { return aIndex; } else { return Math.min(aIndex, bIndex); } } @Override public TreeAnnotator createDependentTypesTreeAnnotator(AnnotatedTypeFactory factory) { return new DependentTypesTreeAnnotator(factory, this) { @Override public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) { // UpperBoundTreeAnnotator changes the type of array.length to @LTEL // ("array"). If the DependentTypesTreeAnnotator tries to viewpoint // adapt it based on the declaration of length; it will fail. if (TreeUtils.isArrayLengthAccess(tree)) { return null; } return super.visitMemberSelect(tree, type); } }; } }; } @Override public AnnotationMirror aliasedAnnotation(AnnotationMirror a) { if (AnnotationUtils.areSameByClass(a, IndexFor.class) || AnnotationUtils.areSameByClass(a, SearchIndexFor.class) || AnnotationUtils.areSameByClass(a, NegativeIndexFor.class)) { List<String> stringList = AnnotationUtils.getElementValueArray(a, "value", String.class, true); return createLTLengthOfAnnotation(stringList.toArray(new String[0])); } if (AnnotationUtils.areSameByClass(a, IndexOrLow.class)) { List<String> stringList = AnnotationUtils.getElementValueArray(a, "value", String.class, true); return createLTLengthOfAnnotation(stringList.toArray(new String[0])); } if (AnnotationUtils.areSameByClass(a, IndexOrHigh.class) || AnnotationUtils.areSameByClass(a, LengthOf.class)) { List<String> stringList = AnnotationUtils.getElementValueArray(a, "value", String.class, true); return createLTEqLengthOfAnnotation(stringList.toArray(new String[0])); } return super.aliasedAnnotation(a); } /** * Queries the SameLen Checker to return the type that the SameLen Checker associates with the * given tree. */ public AnnotationMirror sameLenAnnotationFromTree(Tree tree) { AnnotatedTypeMirror sameLenType = getSameLenAnnotatedTypeFactory().getAnnotatedType(tree); return sameLenType.getAnnotation(SameLen.class); } // Wrapper methods for accessing the IndexMethodIdentifier. public boolean isMathMin(Tree methodTree) { return imf.isMathMin(methodTree, processingEnv); } public boolean isRandomNextInt(Tree methodTree) { return imf.isRandomNextInt(methodTree, processingEnv); } AnnotationMirror createLTLengthOfAnnotation(String... names) { AnnotationBuilder builder = new AnnotationBuilder(getProcessingEnv(), LTLengthOf.class); if (names == null) { names = new String[0]; } builder.setValue("value", names); return builder.build(); } AnnotationMirror createLTEqLengthOfAnnotation(String... names) { AnnotationBuilder builder = new AnnotationBuilder(getProcessingEnv(), LTEqLengthOf.class); if (names == null) { names = new String[0]; } builder.setValue("value", names); return builder.build(); } /** * Returns true iff the given node has the passed Lower Bound qualifier according to the LBC. * The last argument should be Positive.class, NonNegative.class, or GTENegativeOne.class. */ public boolean hasLowerBoundTypeByClass(Node node, Class<? extends Annotation> classOfType) { return AnnotationUtils.areSameByClass( getLowerBoundAnnotatedTypeFactory() .getAnnotatedType(node.getTree()) .getAnnotationInHierarchy(getLowerBoundAnnotatedTypeFactory().UNKNOWN), classOfType); } @Override public QualifierHierarchy createQualifierHierarchy(MultiGraphFactory factory) { return new UpperBoundQualifierHierarchy(factory); } /** * The qualifier hierarchy for the upperbound type system. The qh is responsible for determining * the relationships within the qualifiers - especially subtyping relations. */ protected final class UpperBoundQualifierHierarchy extends MultiGraphQualifierHierarchy { /** @param factory MultiGraphFactory to use to construct this */ public UpperBoundQualifierHierarchy( MultiGraphQualifierHierarchy.MultiGraphFactory factory) { super(factory); } @Override public AnnotationMirror greatestLowerBound(AnnotationMirror a1, AnnotationMirror a2) { UBQualifier a1Obj = UBQualifier.createUBQualifier(a1); UBQualifier a2Obj = UBQualifier.createUBQualifier(a2); UBQualifier glb = a1Obj.glb(a2Obj); return convertUBQualifierToAnnotation(glb); } /** * Determines the least upper bound of a1 and a2. If a1 and a2 are both the same type of * Value annotation, then the LUB is the result of taking the intersection of values from * both a1 and a2. * * @return the least upper bound of a1 and a2 */ @Override public AnnotationMirror leastUpperBound(AnnotationMirror a1, AnnotationMirror a2) { UBQualifier a1Obj = UBQualifier.createUBQualifier(a1); UBQualifier a2Obj = UBQualifier.createUBQualifier(a2); UBQualifier lub = a1Obj.lub(a2Obj); return convertUBQualifierToAnnotation(lub); } @Override public AnnotationMirror widenUpperBound(AnnotationMirror a, AnnotationMirror b) { UBQualifier a1Obj = UBQualifier.createUBQualifier(a); UBQualifier a2Obj = UBQualifier.createUBQualifier(b); UBQualifier lub = a1Obj.widenUpperBound(a2Obj); return convertUBQualifierToAnnotation(lub); } @Override public boolean implementsWidening() { return true; } /** * Computes subtyping as per the subtyping in the qualifier hierarchy structure unless both * annotations are the same. In this case, rhs is a subtype of lhs iff rhs contains at least * every element of lhs. * * @return true if rhs is a subtype of lhs, false otherwise */ @Override public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) { UBQualifier subtype = UBQualifier.createUBQualifier(subAnno); UBQualifier supertype = UBQualifier.createUBQualifier(superAnno); return subtype.isSubtype(supertype); } } @Override public TreeAnnotator createTreeAnnotator() { return new ListTreeAnnotator( new UpperBoundTreeAnnotator(this), super.createTreeAnnotator()); } protected class UpperBoundTreeAnnotator extends TreeAnnotator { public UpperBoundTreeAnnotator(UpperBoundAnnotatedTypeFactory factory) { super(factory); } /** * This exists for Math.min and Random.nextInt, which must be special-cased. * * <ul> * <li>Math.min has unusual semantics that combines annotations for the UBC. * <li>The return type of Random.nextInt depends on the argument, but is not equal to it, * so a polymorhpic qualifier is insufficient. * </ul> * * Other methods should not be special-cased here unless there is a compelling reason to do * so. */ @Override public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { if (isMathMin(tree)) { AnnotatedTypeMirror leftType = getAnnotatedType(tree.getArguments().get(0)); AnnotatedTypeMirror rightType = getAnnotatedType(tree.getArguments().get(1)); type.replaceAnnotation( qualHierarchy.greatestLowerBound( leftType.getAnnotationInHierarchy(UNKNOWN), rightType.getAnnotationInHierarchy(UNKNOWN))); } if (isRandomNextInt(tree)) { AnnotatedTypeMirror argType = getAnnotatedType(tree.getArguments().get(0)); AnnotationMirror anno = argType.getAnnotationInHierarchy(UNKNOWN); UBQualifier qualifier = UBQualifier.createUBQualifier(anno); qualifier = qualifier.plusOffset(1); type.replaceAnnotation(convertUBQualifierToAnnotation(qualifier)); } return super.visitMethodInvocation(tree, type); } @Override public Void visitUnary(UnaryTree node, AnnotatedTypeMirror type) { // Dataflow refines this type if possible if (node.getKind() == Kind.BITWISE_COMPLEMENT) { addAnnotationForBitwiseComplement( getSearchIndexAnnotatedTypeFactory().getAnnotatedType(node.getExpression()), type); } else { type.addAnnotation(UNKNOWN); } return super.visitUnary(node, type); } /** * If a type returned by an {@link SearchIndexAnnotatedTypeFactory} has a {@link * NegativeIndexFor} annotation, then refine the result to be {@link LTEqLengthOf}. This * handles this case: * * <pre>{@code * int i = Arrays.binarySearch(a, x); * if (i >= 0) { * // do something * } else { * i = ~i; * // i is now @LTEqLengthOf("a"), because the bitwise complement of a NegativeIndexFor is an LTL. * for (int j = 0; j < i; j++) { * // j is now a valid index for "a" * } * } * }</pre> * * @param searchIndexType The type of an expression in a bitwise complement. For instance, * in {@code ~x}, this is the type of {@code x}. * @param typeDst The type of the entire bitwise complement expression. It is modified by * this method. */ private void addAnnotationForBitwiseComplement( AnnotatedTypeMirror searchIndexType, AnnotatedTypeMirror typeDst) { if (AnnotationUtils.containsSameByClass( searchIndexType.getAnnotations(), NegativeIndexFor.class)) { AnnotationMirror nif = searchIndexType.getAnnotation(NegativeIndexFor.class); List<String> arrays = IndexUtil.getValueOfAnnotationWithStringArgument(nif); List<String> negativeOnes = Collections.nCopies(arrays.size(), "-1"); UBQualifier qual = UBQualifier.createUBQualifier(arrays, negativeOnes); typeDst.addAnnotation(convertUBQualifierToAnnotation(qual)); } else { typeDst.addAnnotation(UNKNOWN); } } @Override public Void visitCompoundAssignment(CompoundAssignmentTree node, AnnotatedTypeMirror type) { // Dataflow refines this type if possible type.addAnnotation(UNKNOWN); return super.visitCompoundAssignment(node, type); } @Override public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { // A few small rules for addition/subtraction by 0/1, etc. if (TreeUtils.isStringConcatenation(tree)) { type.addAnnotation(UNKNOWN); return super.visitBinary(tree, type); } ExpressionTree left = tree.getLeftOperand(); ExpressionTree right = tree.getRightOperand(); switch (tree.getKind()) { case PLUS: case MINUS: // Dataflow refines this type if possible type.addAnnotation(UNKNOWN); break; case MULTIPLY: addAnnotationForMultiply(left, right, type); break; case DIVIDE: addAnnotationForDivide(left, right, type); break; case AND: addAnnotationForAnd(left, right, type); break; default: break; } return super.visitBinary(tree, type); } private void addAnnotationForAnd( ExpressionTree left, ExpressionTree right, AnnotatedTypeMirror type) { AnnotatedTypeMirror leftType = getAnnotatedType(left); AnnotatedTypeMirror leftLBType = getLowerBoundAnnotatedTypeFactory().getAnnotatedType(left); AnnotationMirror leftResultType = UNKNOWN; if (leftLBType.hasAnnotation(NonNegative.class) || leftLBType.hasAnnotation(Positive.class)) { leftResultType = leftType.getAnnotationInHierarchy(UNKNOWN); } AnnotatedTypeMirror rightType = getAnnotatedType(right); AnnotatedTypeMirror rightLBType = getLowerBoundAnnotatedTypeFactory().getAnnotatedType(right); AnnotationMirror rightResultType = UNKNOWN; if (rightLBType.hasAnnotation(NonNegative.class) || rightLBType.hasAnnotation(Positive.class)) { rightResultType = rightType.getAnnotationInHierarchy(UNKNOWN); } type.addAnnotation(qualHierarchy.greatestLowerBound(leftResultType, rightResultType)); } private void addAnnotationForDivide( ExpressionTree numeratorTree, ExpressionTree divisorTree, AnnotatedTypeMirror resultType) { Long divisor = IndexUtil.getExactValue(divisorTree, getValueAnnotatedTypeFactory()); if (divisor == null) { resultType.addAnnotation(UNKNOWN); return; } UBQualifier result = UpperBoundUnknownQualifier.UNKNOWN; UBQualifier numerator = UBQualifier.createUBQualifier(getAnnotatedType(numeratorTree), UNKNOWN); if (numerator.isLessThanLengthQualifier()) { result = ((LessThanLengthOf) numerator).divide(divisor.intValue()); } result = result.glb(plusTreeDivideByVal(divisor.intValue(), numeratorTree)); // If the numerator is an array length access of an array with non-zero length, and the divisor is // greater than one, glb the result with an LTL of the array. if (TreeUtils.isArrayLengthAccess(numeratorTree) && divisor > 1) { String arrayName = ((MemberSelectTree) numeratorTree).getExpression().toString(); int minlen = getValueAnnotatedTypeFactory() .getMinLenFromString( arrayName, numeratorTree, getPath(numeratorTree)); if (minlen > 0) { result = result.glb(UBQualifier.createUBQualifier(arrayName, "0")); } } resultType.addAnnotation(convertUBQualifierToAnnotation(result)); } /** * if numeratorTree is a + b and divisor greater than 1, and a and b are less than the * length of some array, then (a + b) / divisor is less than the length of that array. */ private UBQualifier plusTreeDivideByVal(int divisor, ExpressionTree numeratorTree) { numeratorTree = TreeUtils.skipParens(numeratorTree); if (divisor < 2 || numeratorTree.getKind() != Kind.PLUS) { return UpperBoundUnknownQualifier.UNKNOWN; } BinaryTree plusTree = (BinaryTree) numeratorTree; UBQualifier left = UBQualifier.createUBQualifier( getAnnotatedType(plusTree.getLeftOperand()), UNKNOWN); UBQualifier right = UBQualifier.createUBQualifier( getAnnotatedType(plusTree.getRightOperand()), UNKNOWN); if (left.isLessThanLengthQualifier() && right.isLessThanLengthQualifier()) { LessThanLengthOf leftLTL = (LessThanLengthOf) left; LessThanLengthOf rightLTL = (LessThanLengthOf) right; List<String> arrays = new ArrayList<>(); for (String array : leftLTL.getArrays()) { if (rightLTL.isLessThanLengthOf(array) && leftLTL.isLessThanLengthOf(array)) { arrays.add(array); } } if (!arrays.isEmpty()) { return UBQualifier.createUBQualifier(arrays, Collections.<String>emptyList()); } } return UpperBoundUnknownQualifier.UNKNOWN; } private boolean checkForMathRandomSpecialCase( ExpressionTree randTree, ExpressionTree arrLenTree, AnnotatedTypeMirror type) { if (randTree.getKind() == Tree.Kind.METHOD_INVOCATION && TreeUtils.isArrayLengthAccess(arrLenTree)) { MemberSelectTree mstree = (MemberSelectTree) arrLenTree; MethodInvocationTree mitree = (MethodInvocationTree) randTree; if (imf.isMathRandom(mitree, processingEnv)) { // Okay, so this is Math.random() * array.length, which must be NonNegative type.addAnnotation( createLTLengthOfAnnotation(mstree.getExpression().toString())); return true; } if (imf.isRandomNextDouble(mitree, processingEnv)) { // Okay, so this is Random.nextDouble() * array.length, which must be NonNegative type.addAnnotation( createLTLengthOfAnnotation(mstree.getExpression().toString())); return true; } } return false; } private void addAnnotationForMultiply( ExpressionTree leftExpr, ExpressionTree rightExpr, AnnotatedTypeMirror type) { // Special handling for multiplying an array length by a random variable. if (checkForMathRandomSpecialCase(rightExpr, leftExpr, type) || checkForMathRandomSpecialCase(leftExpr, rightExpr, type)) { return; } type.addAnnotation(UNKNOWN); } } public AnnotationMirror convertUBQualifierToAnnotation(UBQualifier qualifier) { if (qualifier.isUnknown()) { return UNKNOWN; } else if (qualifier.isBottom()) { return BOTTOM; } else if (qualifier.isPoly()) { return POLY; } LessThanLengthOf ltlQualifier = (LessThanLengthOf) qualifier; return ltlQualifier.convertToAnnotationMirror(processingEnv); } }