package org.checkerframework.checker.index.lowerbound; import static org.checkerframework.checker.index.IndexUtil.getExactValue; import static org.checkerframework.checker.index.IndexUtil.getPossibleValues; import com.sun.source.tree.BinaryTree; 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 java.lang.annotation.Annotation; 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.qual.GTENegativeOne; 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.LengthOf; import org.checkerframework.checker.index.qual.LowerBoundUnknown; 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.PolyLowerBound; import org.checkerframework.checker.index.qual.Positive; import org.checkerframework.checker.index.searchindex.SearchIndexAnnotatedTypeFactory; import org.checkerframework.checker.index.searchindex.SearchIndexChecker; 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.framework.qual.PolyAll; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.treeannotator.ImplicitsTreeAnnotator; import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator; import org.checkerframework.framework.type.treeannotator.PropagationTreeAnnotator; import org.checkerframework.framework.type.treeannotator.TreeAnnotator; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.TreeUtils; /** * Implements the introduction rules for the Lower Bound Checker. * * <pre> * The type hierarchy is: * * Top = lbu ("Lower Bound Unknown") * | * gte-1 ("Greater than or equal to -1") * | * nn ("NonNegative") * | * pos ("Positive") * </pre> * * In general, check whether the constant Value Checker can determine the value of a variable; if it * can, use that; if not, use more specific rules based on expression type. These rules are * documented on the functions implementing them. */ public class LowerBoundAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { /** The canonical @{@link GTENegativeOne} annotation. */ public final AnnotationMirror GTEN1 = AnnotationUtils.fromClass(elements, GTENegativeOne.class); /** The canonical @{@link NonNegative} annotation. */ public final AnnotationMirror NN = AnnotationUtils.fromClass(elements, NonNegative.class); /** The canonical @{@link Positive} annotation. */ public final AnnotationMirror POS = AnnotationUtils.fromClass(elements, Positive.class); /** The bottom annotation. */ public final AnnotationMirror BOTTOM = POS; /** The canonical @{@link LowerBoundUnknown} annotation. */ public final AnnotationMirror UNKNOWN = AnnotationUtils.fromClass(elements, LowerBoundUnknown.class); /** The canonical @{@link PolyLowerBound} annotation. */ public final AnnotationMirror POLY = AnnotationUtils.fromClass(elements, PolyLowerBound.class); private final IndexMethodIdentifier imf; public LowerBoundAnnotatedTypeFactory(BaseTypeChecker checker) { super(checker); addAliasedAnnotation(IndexFor.class, NN); addAliasedAnnotation(IndexOrLow.class, GTEN1); addAliasedAnnotation(IndexOrHigh.class, NN); addAliasedAnnotation(LengthOf.class, NN); 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( Positive.class, NonNegative.class, GTENegativeOne.class, LowerBoundUnknown.class, PolyLowerBound.class)); } /** * Takes a value type (only interesting if it's an IntVal), and converts it to a lower bound * type. If the new lower bound type is more specific than type, convert type to that type. * * @param valueType the Value Checker type * @param type the current lower bound type of the expression being evaluated */ private void addLowerBoundTypeFromValueType( AnnotatedTypeMirror valueType, AnnotatedTypeMirror type) { AnnotationMirror anm = getLowerBoundAnnotationFromValueType(valueType); if (!type.isAnnotatedInHierarchy(UNKNOWN)) { if (!AnnotationUtils.areSameByClass(anm, LowerBoundUnknown.class)) { type.addAnnotation(anm); } return; } if (qualHierarchy.isSubtype(anm, type.getAnnotationInHierarchy(UNKNOWN))) { type.replaceAnnotation(anm); } } @Override public void addComputedTypeAnnotations(Element element, AnnotatedTypeMirror type) { super.addComputedTypeAnnotations(element, type); if (element != null) { AnnotatedTypeMirror valueType = getValueAnnotatedTypeFactory().getAnnotatedType(element); addLowerBoundTypeFromValueType(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); addLowerBoundTypeFromValueType(valueType, type); } } /** Returns the Value Checker's annotated type factory. */ public ValueAnnotatedTypeFactory getValueAnnotatedTypeFactory() { return getTypeFactoryOfSubchecker(ValueChecker.class); } /** Returns the SearchIndexFor Checker's annotated type factory. */ public SearchIndexAnnotatedTypeFactory getSearchIndexAnnotatedTypeFactory() { return getTypeFactoryOfSubchecker(SearchIndexChecker.class); } /** Returns the type in the lower bound hierarchy that a Value Checker type corresponds to. */ private AnnotationMirror getLowerBoundAnnotationFromValueType(AnnotatedTypeMirror valueType) { List<Long> possibleValues = getPossibleValues(valueType); // possibleValues is null if the Value Checker does not have any estimate. if (possibleValues == null) { // possibleValues is null if there is no IntVal annotation on the type - such as // when there is a BottomVal annotation. In that case, give this the LBC's bottom type. if (AnnotationUtils.containsSameByClass(valueType.getAnnotations(), BottomVal.class)) { return POS; } return UNKNOWN; } if (possibleValues.size() == 0) { return BOTTOM; } // The annotation of the whole list is the min of the list. long lvalMin = Collections.min(possibleValues); // Turn it into an integer. int valMin = (int) Math.max(Math.min(Integer.MAX_VALUE, lvalMin), Integer.MIN_VALUE); return anmFromVal(valMin); } /** Determine the annotation that should be associated with a literal. */ private AnnotationMirror anmFromVal(long val) { if (val >= 1) { return POS; } else if (val >= 0) { return NN; } else if (val >= -1) { return GTEN1; } else { return UNKNOWN; } } @Override public TreeAnnotator createTreeAnnotator() { return new ListTreeAnnotator( new LowerBoundTreeAnnotator(this), new PropagationTreeAnnotator(this), new ImplicitsTreeAnnotator(this)); } private class LowerBoundTreeAnnotator extends TreeAnnotator { public LowerBoundTreeAnnotator(AnnotatedTypeFactory annotatedTypeFactory) { super(annotatedTypeFactory); } /** * Sets typeDst to the immediate supertype of typeSrc, unless typeSrc is already Positive. * Implements the following transitions: * * <pre> * pos → pos * nn → pos * gte-1 → nn * lbu → lbu * </pre> */ private void promoteType(AnnotatedTypeMirror typeSrc, AnnotatedTypeMirror typeDst) { if (typeSrc.hasAnnotation(POS)) { typeDst.replaceAnnotation(POS); } else if (typeSrc.hasAnnotation(NN)) { typeDst.replaceAnnotation(POS); } else if (typeSrc.hasAnnotation(GTEN1)) { typeDst.replaceAnnotation(NN); } else { //Only unknown is left. typeDst.replaceAnnotation(UNKNOWN); } } /** * Sets typeDst to the immediate subtype of typeSrc, unless typeSrc is already * LowerBoundUnknown. Implements the following transitions: * * <pre> * pos → nn * nn → gte-1 * gte-1, lbu → lbu * </pre> */ private void demoteType(AnnotatedTypeMirror typeSrc, AnnotatedTypeMirror typeDst) { if (typeSrc.hasAnnotation(POS)) { typeDst.replaceAnnotation(NN); } else if (typeSrc.hasAnnotation(NN)) { typeDst.replaceAnnotation(GTEN1); } else { // GTEN1 and UNKNOWN both become UNKNOWN. typeDst.replaceAnnotation(UNKNOWN); } } /** Call increment and decrement helper functions. */ @Override public Void visitUnary(UnaryTree tree, AnnotatedTypeMirror typeDst) { AnnotatedTypeMirror typeSrc = getAnnotatedType(tree.getExpression()); switch (tree.getKind()) { case PREFIX_INCREMENT: promoteType(typeSrc, typeDst); break; case PREFIX_DECREMENT: demoteType(typeSrc, typeDst); break; case POSTFIX_INCREMENT: case POSTFIX_DECREMENT: // Do nothing. The CF should take care of these itself. break; case BITWISE_COMPLEMENT: handleBitWiseComplement( getSearchIndexAnnotatedTypeFactory() .getAnnotatedType(tree.getExpression()), typeDst); break; default: break; } return super.visitUnary(tree, typeDst); } /** * Bitwise complement converts between {@code @NegativeIndexFor} and {@code @IndexOrHigh}. * This handles the lowerbound part of that type, so the result is converted to * {@code @NonNegative}. * * @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 handleBitWiseComplement( AnnotatedTypeMirror searchIndexType, AnnotatedTypeMirror typeDst) { if (AnnotationUtils.containsSameByClass( searchIndexType.getAnnotations(), NegativeIndexFor.class)) { typeDst.addAnnotation(NN); } } /** Special handling for Math.max. The return is the GLB of the arguments. */ @Override public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { if (imf.isMathMax(tree, processingEnv)) { ExpressionTree left = tree.getArguments().get(0); ExpressionTree right = tree.getArguments().get(1); type.replaceAnnotation( qualHierarchy.greatestLowerBound( getAnnotatedType(left).getAnnotationInHierarchy(POS), getAnnotatedType(right).getAnnotationInHierarchy(POS))); } return super.visitMethodInvocation(tree, type); } /** * Looks up the minlen of a member select tree. Returns null if the tree doesn't represent * an array's length field. */ private Integer getMinLenFromMemberSelectTree(MemberSelectTree tree) { if (TreeUtils.isArrayLengthAccess(tree)) { AnnotatedTypeMirror minLenType = getValueAnnotatedTypeFactory().getAnnotatedType(tree); Long min = getValueAnnotatedTypeFactory().getMinimumIntegralValue(minLenType); if (min == null) { return null; } if (min < 0 || min > Integer.MAX_VALUE) { min = 0L; } return min.intValue(); } return null; } /** * For dealing with array length expressions. Looks for array length accesses specifically, * then dispatches to the MinLen checker to determine the length of the relevant array. If * it's found, use it to give the expression a type. */ @Override public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) { Integer minLen = getMinLenFromMemberSelectTree(tree); if (minLen != null) { type.replaceAnnotation(anmFromVal(minLen)); } return super.visitMemberSelect(tree, type); } /** * Dispatch to binary operator helper methods. The Lower Bound Checker currently handles * addition, subtraction, multiplication, division, and remainder. */ @Override public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { // Check if this is a string concatenation. If so, bail. if (TreeUtils.isStringConcatenation(tree)) { type.addAnnotation(UNKNOWN); return super.visitBinary(tree, type); } // Dispatch according to the operation. ExpressionTree left = tree.getLeftOperand(); ExpressionTree right = tree.getRightOperand(); // Every "addAnnotationForX" method is stateful and modifies the variable "type". switch (tree.getKind()) { case PLUS: addAnnotationForPlus(left, right, type); break; case MINUS: addAnnotationForMinus(left, right, type); break; case MULTIPLY: addAnnotationForMultiply(left, right, type); break; case DIVIDE: addAnnotationForDivide(left, right, type); break; case REMAINDER: addAnnotationForRemainder(left, right, type); break; case AND: addAnnotationForAnd(left, right, type); break; case RIGHT_SHIFT: addAnnotationForRightShift(left, right, type); break; default: break; } return super.visitBinary(tree, type); } /** * Helper method for addAnnotationForPlus. Handles addition of constants. * * @param val the integer value of the constant * @param nonLiteralType the type of the side of the expression that isn't a constant * @param type the type of the result expression */ private void addAnnotationForLiteralPlus( int val, AnnotatedTypeMirror nonLiteralType, AnnotatedTypeMirror type) { if (val == -2) { if (nonLiteralType.hasAnnotation(POS)) { type.addAnnotation(GTEN1); return; } } else if (val == -1) { demoteType(nonLiteralType, type); return; } else if (val == 0) { // This gets the type of nonLiteralType in our hierarchy (in which POS is the // bottom type). type.addAnnotation(nonLiteralType.getAnnotationInHierarchy(POS)); return; } else if (val == 1) { promoteType(nonLiteralType, type); return; } else if (val >= 2) { if (!nonLiteralType.hasAnnotation(UNKNOWN)) { // 2 + a positive, or a non-negative, or a non-negative-1 is a positive type.addAnnotation(POS); return; } } type.addAnnotation(UNKNOWN); } /** * addAnnotationForPlus handles the following cases: * * <pre> * lit -2 + pos → gte-1 * lit -1 + * → call demote * lit 0 + * → * * lit 1 + * → call promote * lit ≥ 2 + {gte-1, nn, or pos} → pos * let all other lits, including sets, fall through: * pos + pos → pos * nn + * → * * pos + gte-1 → nn * * + * → lbu * </pre> */ private void addAnnotationForPlus( ExpressionTree leftExpr, ExpressionTree rightExpr, AnnotatedTypeMirror type) { // Adding two literals is handled by visitBinary, so that // case can be ignored. AnnotatedTypeMirror leftType = getAnnotatedType(leftExpr); // Check if the right side's value is known at compile time. Long valRight = getExactValue(rightExpr, getValueAnnotatedTypeFactory()); if (valRight != null) { addAnnotationForLiteralPlus(valRight.intValue(), leftType, type); return; } AnnotatedTypeMirror rightType = getAnnotatedType(rightExpr); // Check if the left side's value is known at compile time. Long valLeft = getExactValue(leftExpr, getValueAnnotatedTypeFactory()); if (valLeft != null) { addAnnotationForLiteralPlus(valLeft.intValue(), rightType, type); return; } /* This section is handling the generic cases: * pos + pos -> pos * nn + * -> * * pos + gte-1 -> nn */ if (leftType.hasAnnotation(POS) && rightType.hasAnnotation(POS)) { type.addAnnotation(POS); return; } if (leftType.hasAnnotation(NN)) { type.addAnnotation(rightType.getAnnotationInHierarchy(POS)); return; } if (rightType.hasAnnotation(NN)) { type.addAnnotation(leftType.getAnnotationInHierarchy(POS)); return; } if ((leftType.hasAnnotation(POS) && rightType.hasAnnotation(GTEN1)) || (leftType.hasAnnotation(GTEN1) && rightType.hasAnnotation(POS))) { type.addAnnotation(NN); return; } // * + * -> lbu. type.addAnnotation(UNKNOWN); } /** * addAnnotationForMinus handles the following cases: * * <pre> * * - lit → call plus(*, -1 * the value of the lit) * * - * → lbu * </pre> */ private void addAnnotationForMinus( ExpressionTree leftExpr, ExpressionTree rightExpr, AnnotatedTypeMirror type) { // Check if the right side's value is known at compile time. Long valRight = getExactValue(rightExpr, getValueAnnotatedTypeFactory()); if (valRight != null) { AnnotatedTypeMirror leftType = getAnnotatedType(leftExpr); // Instead of a separate method for subtraction, add the negative of a constant. addAnnotationForLiteralPlus(-1 * valRight.intValue(), leftType, type); // Check if the left side is a field access of an array's length. If so, // try to look up the MinLen of the array, and potentially keep // this either NN or POS instead of GTEN1 or LBU. if (leftExpr.getKind() == Kind.MEMBER_SELECT) { MemberSelectTree mstree = (MemberSelectTree) leftExpr; Integer minLen = getMinLenFromMemberSelectTree(mstree); if (minLen != null) { type.replaceAnnotation(anmFromVal(minLen - valRight)); } } return; } // The checker can't reason about arbitrary (i.e. non-literal) // things that are being subtracted, so it gives up. type.addAnnotation(UNKNOWN); } /** * Helper function for addAnnotationForMultiply. Handles compile-time known constants. * * @param val the integer value of the constant * @param nonLiteralType the type of the side of the expression that isn't a constant * @param type the type of the result expression */ private void addAnnotationForLiteralMultiply( int val, AnnotatedTypeMirror nonLiteralType, AnnotatedTypeMirror type) { if (val == 0) { type.addAnnotation(NN); return; } else if (val == 1) { // Make the result type equal to nonLiteralType. type.addAnnotation(nonLiteralType.getAnnotationInHierarchy(POS)); return; } else if (val > 1) { if (nonLiteralType.hasAnnotation(POS) || nonLiteralType.hasAnnotation(NN)) { type.addAnnotation(nonLiteralType.getAnnotationInHierarchy(POS)); return; } } type.addAnnotation(UNKNOWN); } private boolean checkForMathRandomSpecialCase( ExpressionTree randTree, ExpressionTree arrLenTree, AnnotatedTypeMirror type) { if (randTree.getKind() == Kind.METHOD_INVOCATION && TreeUtils.isArrayLengthAccess(arrLenTree)) { MethodInvocationTree miTree = (MethodInvocationTree) randTree; if (imf.isMathRandom(miTree, processingEnv)) { // This is Math.random() * array.length, which must be NonNegative type.addAnnotation(NN); return true; } if (imf.isRandomNextDouble(miTree, processingEnv)) { // This is Random.nextDouble() * array.length, which must be NonNegative type.addAnnotation(NN); return true; } } return false; } /** * addAnnotationForMultiply handles the following cases: * * <pre> * * * lit 0 → nn (=0) * * * lit 1 → * * pos * pos → pos * pos * nn → nn * nn * nn → nn * * * * → lbu * </pre> */ private void addAnnotationForMultiply( ExpressionTree leftExpr, ExpressionTree rightExpr, AnnotatedTypeMirror type) { // Special handling for multiplying an array length by a Math.random(). if (checkForMathRandomSpecialCase(rightExpr, leftExpr, type) || checkForMathRandomSpecialCase(leftExpr, rightExpr, type)) { return; } AnnotatedTypeMirror leftType = getAnnotatedType(leftExpr); // Check if the right side's value is known at compile time. Long valRight = getExactValue(rightExpr, getValueAnnotatedTypeFactory()); if (valRight != null) { addAnnotationForLiteralMultiply(valRight.intValue(), leftType, type); return; } AnnotatedTypeMirror rightType = getAnnotatedType(rightExpr); // Check if the left side's value is known at compile time. Long valLeft = getExactValue(leftExpr, getValueAnnotatedTypeFactory()); if (valLeft != null) { addAnnotationForLiteralMultiply(valLeft.intValue(), rightType, type); return; } /* This section handles generic annotations: * pos * pos -> pos * nn * pos -> nn * nn * nn -> nn */ if (leftType.hasAnnotation(POS) && rightType.hasAnnotation(POS)) { type.addAnnotation(POS); return; } if ((leftType.hasAnnotation(POS) && rightType.hasAnnotation(NN)) || (leftType.hasAnnotation(NN) && rightType.hasAnnotation(POS))) { type.addAnnotation(NN); return; } if (leftType.hasAnnotation(NN) && rightType.hasAnnotation(NN)) { type.addAnnotation(NN); return; } type.addAnnotation(UNKNOWN); } /** When the value on the left is known at compile time. */ private void addAnnotationForLiteralDivideLeft( int val, AnnotatedTypeMirror rightType, AnnotatedTypeMirror type) { if (val == 0) { type.addAnnotation(NN); } else if (val == 1) { if (rightType.hasAnnotation(NN) || rightType.hasAnnotation(POS)) { type.addAnnotation(NN); } else { // (1 / x) can't be outside the range [-1, 1] when x is an integer. type.addAnnotation(GTEN1); } } } /** When the value on the right is known at compile time. */ private void addAnnotationForLiteralDivideRight( int val, AnnotatedTypeMirror leftType, AnnotatedTypeMirror type) { if (val == 0) { // Reaching this indicates a divide by zero error. If the value is zero, then this is // division by zero. Division by zero is treated as bottom (i.e. Positive) so that users // aren't warned about dead code that's dividing by zero. This code assumes that non-dead // code won't include literal divide by zeros... type.addAnnotation(POS); } else if (val == 1) { type.addAnnotation(leftType.getAnnotationInHierarchy(POS)); } else if (val >= 2) { if (leftType.hasAnnotation(NonNegative.class) || leftType.hasAnnotation(Positive.class)) { type.addAnnotation(NN); } } } /** * addAnnotationForDivide handles these cases: * * <pre> * lit 0 / * → nn (=0) * * / lit 0 → pos * lit 1 / {pos, nn} → nn * lit 1 / * → gten1 * * / lit 1 → * * {pos, nn} / lit >1 → nn * pos / {pos, nn} → nn (can round to zero) * * / {pos, nn} → * * * / * → lbu * </pre> */ private void addAnnotationForDivide( ExpressionTree leftExpr, ExpressionTree rightExpr, AnnotatedTypeMirror type) { AnnotatedTypeMirror leftType = getAnnotatedType(leftExpr); // Check if the right side's value is known at compile time. Long valRight = getExactValue(rightExpr, getValueAnnotatedTypeFactory()); if (valRight != null) { addAnnotationForLiteralDivideRight(valRight.intValue(), leftType, type); return; } AnnotatedTypeMirror rightType = getAnnotatedType(rightExpr); // Check if the left side's value is known at compile time. Long valLeft = getExactValue(leftExpr, getValueAnnotatedTypeFactory()); if (valLeft != null) { addAnnotationForLiteralDivideLeft(valLeft.intValue(), leftType, type); return; } /* This section handles generic annotations: * pos / {pos, nn} -> nn (can round to zero) * * / {pos, nn} -> * */ if (leftType.hasAnnotation(POS) && (rightType.hasAnnotation(POS) || rightType.hasAnnotation(NN))) { type.addAnnotation(NN); return; } if (rightType.hasAnnotation(POS) || rightType.hasAnnotation(NN)) { type.addAnnotation(leftType.getAnnotationInHierarchy(POS)); return; } // Everything else is unknown. type.addAnnotation(UNKNOWN); } /** A remainder with 1 or -1 as the divisor always results in zero. */ private void addAnnotationForLiteralRemainder(int val, AnnotatedTypeMirror type) { if (val == 1 || val == -1) { type.addAnnotation(NN); } } /** * addAnnotationForRemainder handles these cases: * % 1/-1 → nn pos/nn % * → nn * gten1 % * → gten1 * % * → lbu */ public void addAnnotationForRemainder( ExpressionTree leftExpr, ExpressionTree rightExpr, AnnotatedTypeMirror type) { AnnotatedTypeMirror leftType = getAnnotatedType(leftExpr); AnnotatedTypeMirror rightType = getAnnotatedType(rightExpr); // Check if the right side's value is known at compile time. Long valRight = getExactValue(rightExpr, getValueAnnotatedTypeFactory()); if (valRight != null) { addAnnotationForLiteralRemainder(valRight.intValue(), type); } /* This section handles generic annotations: pos/nn % * -> nn gten1 % * -> gten1 */ if (leftType.hasAnnotation(POS) || leftType.hasAnnotation(NN)) { type.addAnnotation(NN); return; } if (leftType.hasAnnotation(GTEN1)) { type.addAnnotation(GTEN1); return; } // Everything else is unknown. type.addAnnotation(UNKNOWN); } } /** Handles shifts. * >> NonNegative → NonNegative */ private void addAnnotationForRightShift( ExpressionTree leftExpr, ExpressionTree rightExpr, AnnotatedTypeMirror type) { AnnotatedTypeMirror rightType = getAnnotatedType(rightExpr); AnnotatedTypeMirror leftType = getAnnotatedType(leftExpr); if (leftType.hasAnnotation(NN) || leftType.hasAnnotation(POS)) { if (rightType.hasAnnotation(NN) || rightType.hasAnnotation(POS)) { type.addAnnotation(NN); return; } } type.addAnnotation(UNKNOWN); } /** * Handles masking. Particularly, handles the following cases: * & NonNegative → * NonNegative */ private void addAnnotationForAnd( ExpressionTree leftExpr, ExpressionTree rightExpr, AnnotatedTypeMirror type) { AnnotatedTypeMirror rightType = getAnnotatedType(rightExpr); if (rightType.hasAnnotation(NN) || rightType.hasAnnotation(POS)) { type.addAnnotation(NN); return; } AnnotatedTypeMirror leftType = getAnnotatedType(leftExpr); if (leftType.hasAnnotation(NN) || leftType.hasAnnotation(POS)) { type.addAnnotation(NN); return; } type.addAnnotation(UNKNOWN); } }