blob: c0d7b693a18d7d99f067c3eba4085a8f00dc1de6 [file] [log] [blame]
package org.checkerframework.checker.index.lowerbound;
import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.LiteralTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.UnaryTree;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import org.checkerframework.checker.index.BaseAnnotatedTypeFactoryForIndexChecker;
import org.checkerframework.checker.index.IndexMethodIdentifier;
import org.checkerframework.checker.index.inequality.LessThanAnnotatedTypeFactory;
import org.checkerframework.checker.index.inequality.LessThanChecker;
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.LowerBoundBottom;
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.qual.SubstringIndexFor;
import org.checkerframework.checker.index.searchindex.SearchIndexAnnotatedTypeFactory;
import org.checkerframework.checker.index.searchindex.SearchIndexChecker;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.common.value.ValueAnnotatedTypeFactory;
import org.checkerframework.common.value.ValueChecker;
import org.checkerframework.common.value.ValueCheckerUtils;
import org.checkerframework.common.value.qual.BottomVal;
import org.checkerframework.common.value.util.Range;
import org.checkerframework.dataflow.cfg.node.NumericalMultiplicationNode;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
import org.checkerframework.javacutil.AnnotationBuilder;
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. This class implements
* the following type rules:
*
* <ul>
* <li>1. If the value checker type for any expression is &ge; 1, refine that expression's type to
* positive.
* <li>2. If the value checker type for any expression is &ge; 0 and case 1 did not apply, then
* refine that expression's type to non-negative.
* <li>3. If the value checker type for any expression is &ge; -1 and cases 1 and 2 did not apply,
* then refine that expression's type to GTEN1.
* <li>4. A unary prefix decrement shifts the type "down" in the hierarchy (i.e. {@code --i} when
* {@code i} is non-negative implies that {@code i} will be GTEN1 afterwards). Should this be
* 3 rules?
* <li>5. A unary prefix increment shifts the type "up" in the hierarchy (i.e. {@code ++i} when
* {@code i} is non-negative implies that {@code i} will be positive afterwards). Should this
* be 3 rules?
* <li>6. Unary negation on a NegativeIndexFor from the SearchIndex type system results in a
* non-negative.
* <li>7. The result of a call to Math.max is the GLB of its arguments.
* <li>8. If an array has a MinLen type &ge; 1 and its length is accessed, the length expression
* is positive.
* </ul>
*/
public class LowerBoundAnnotatedTypeFactory extends BaseAnnotatedTypeFactoryForIndexChecker {
/** The canonical @{@link GTENegativeOne} annotation. */
public final AnnotationMirror GTEN1 = AnnotationBuilder.fromClass(elements, GTENegativeOne.class);
/** The canonical @{@link NonNegative} annotation. */
public final AnnotationMirror NN = AnnotationBuilder.fromClass(elements, NonNegative.class);
/** The canonical @{@link Positive} annotation. */
public final AnnotationMirror POS = AnnotationBuilder.fromClass(elements, Positive.class);
/** The bottom annotation. */
public final AnnotationMirror BOTTOM =
AnnotationBuilder.fromClass(elements, LowerBoundBottom.class);
/** The canonical @{@link LowerBoundUnknown} annotation. */
public final AnnotationMirror UNKNOWN =
AnnotationBuilder.fromClass(elements, LowerBoundUnknown.class);
/** The canonical @{@link PolyLowerBound} annotation. */
public final AnnotationMirror POLY = AnnotationBuilder.fromClass(elements, PolyLowerBound.class);
/** Predicates about method calls. */
private final IndexMethodIdentifier imf;
/**
* Create a new LowerBoundAnnotatedTypeFactory.
*
* @param checker the type-checker
*/
public LowerBoundAnnotatedTypeFactory(BaseTypeChecker checker) {
super(checker);
// Any annotations that are aliased to @NonNegative, @Positive, or @GTENegativeOne must also be
// aliased in the constructor of ValueAnnotatedTypeFactory to the appropriate @IntRangeFrom*
// annotation.
addAliasedTypeAnnotation(IndexFor.class, NN);
addAliasedTypeAnnotation(IndexOrLow.class, GTEN1);
addAliasedTypeAnnotation(IndexOrHigh.class, NN);
addAliasedTypeAnnotation(LengthOf.class, NN);
addAliasedTypeAnnotation(PolyIndex.class, POLY);
addAliasedTypeAnnotation(SubstringIndexFor.class, GTEN1);
imf = new IndexMethodIdentifier(this);
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,
LowerBoundBottom.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 (!areSameByClass(anm, LowerBoundUnknown.class)) {
type.addAnnotation(anm);
}
return;
}
if (qualHierarchy.isSubtype(anm, type.getAnnotationInHierarchy(UNKNOWN))) {
type.replaceAnnotation(anm);
}
}
/** Handles cases 1, 2, and 3. */
@Override
public void addComputedTypeAnnotations(Element element, AnnotatedTypeMirror type) {
super.addComputedTypeAnnotations(element, type);
if (element != null) {
AnnotatedTypeMirror valueType = getValueAnnotatedTypeFactory().getAnnotatedType(element);
addLowerBoundTypeFromValueType(valueType, type);
}
}
/** Handles cases 1, 2, and 3. */
@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 (tree != null
&& TreeUtils.isExpressionTree(tree)
&& (iUseFlow || tree instanceof LiteralTree)) {
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 LessThan Checker's annotated type factory. */
public LessThanAnnotatedTypeFactory getLessThanAnnotatedTypeFactory() {
return getTypeFactoryOfSubchecker(LessThanChecker.class);
}
/** Returns the type in the lower bound hierarchy that a Value Checker type corresponds to. */
private AnnotationMirror getLowerBoundAnnotationFromValueType(AnnotatedTypeMirror valueType) {
Range possibleValues =
ValueCheckerUtils.getPossibleValues(valueType, getValueAnnotatedTypeFactory());
// 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 (containsSameByClass(valueType.getAnnotations(), BottomVal.class)) {
return BOTTOM;
}
return UNKNOWN;
}
// The annotation of the whole list is the min of the list.
long lvalMin = possibleValues.from;
// 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. */
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), super.createTreeAnnotator());
}
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 &rarr; pos
* nn &rarr; pos
* gte-1 &rarr; nn
* lbu &rarr; 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 &rarr; nn
* nn &rarr; gte-1
* gte-1, lbu &rarr; 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. Handles cases 4, 5 and 6. */
@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 (containsSameByClass(searchIndexType.getAnnotations(), NegativeIndexFor.class)) {
typeDst.addAnnotation(NN);
}
}
/** Special handling for Math.max. The return is the GLB of the arguments. Case 7. */
@Override
public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) {
if (imf.isMathMax(tree)) {
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);
}
/**
* 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. Case 8.
*/
@Override
public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) {
Integer minLen = getMinLenFromMemberSelectTree(tree);
if (minLen != null) {
type.replaceAnnotation(anmFromVal(minLen));
}
return super.visitMemberSelect(tree, type);
}
/**
* Does not dispatch to binary operator helper methods. The Lower Bound Checker handles binary
* operations via its transfer function.
*/
@Override
public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) {
type.addAnnotation(UNKNOWN);
return super.visitBinary(tree, type);
}
}
/**
* Looks up the minlen of a member select tree. Returns null if the tree doesn't represent an
* array's length field.
*/
Integer getMinLenFromMemberSelectTree(MemberSelectTree tree) {
if (TreeUtils.isArrayLengthAccess(tree)) {
return ValueCheckerUtils.getMinLenFromTree(tree, getValueAnnotatedTypeFactory());
}
return null;
}
/**
* Looks up the minlen of a method invocation tree. Returns null if the tree doesn't represent an
* string length method.
*/
Integer getMinLenFromMethodInvocationTree(MethodInvocationTree tree) {
if (imf.isLengthOfMethodInvocation(tree)) {
return ValueCheckerUtils.getMinLenFromTree(tree, getValueAnnotatedTypeFactory());
}
return null;
}
/**
* Given a multiplication, return its type if the LBC special-cases it, or null otherwise.
*
* <p>The LBC special-cases {@code Math.random() * array.length} and {@code Random.nextDouble() *
* array.length}.
*
* @param node a multiplication node that may need special casing
* @return an AnnotationMirror representing the result if the special case is valid, or null if
* not
*/
AnnotationMirror checkForMathRandomSpecialCase(NumericalMultiplicationNode node) {
AnnotationMirror forwardRes =
checkForMathRandomSpecialCase(
node.getLeftOperand().getTree(), node.getRightOperand().getTree());
if (forwardRes != null) {
return forwardRes;
}
AnnotationMirror backwardsRes =
checkForMathRandomSpecialCase(
node.getRightOperand().getTree(), node.getLeftOperand().getTree());
if (backwardsRes != null) {
return backwardsRes;
}
return null;
}
/**
* Return true if randTree is a call to Math.random() or Random.nextDouble(), and arrLenTree is
* someArray.length.
*/
private AnnotationMirror checkForMathRandomSpecialCase(Tree randTree, Tree arrLenTree) {
if (randTree.getKind() == Tree.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
return NN;
}
if (imf.isRandomNextDouble(miTree, processingEnv)) {
// This is Random.nextDouble() * array.length, which must be NonNegative
return NN;
}
}
return null;
}
/** Checks if the expression is non-negative, i.e. it has Positive on NonNegative annotation. */
public boolean isNonNegative(Tree tree) {
// TODO: consolidate with the isNonNegative method in LowerBoundTransfer
AnnotatedTypeMirror treeType = getAnnotatedType(tree);
return treeType.hasAnnotation(NonNegative.class) || treeType.hasAnnotation(Positive.class);
}
}