blob: 7694cde8f15345ceb885bdec59c5add5a7e9ccfb [file] [log] [blame]
package org.checkerframework.checker.index.inequality;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.Tree;
import com.sun.source.util.TreePath;
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.ExecutableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.util.Elements;
import org.checkerframework.checker.index.BaseAnnotatedTypeFactoryForIndexChecker;
import org.checkerframework.checker.index.OffsetDependentTypesHelper;
import org.checkerframework.checker.index.qual.LessThan;
import org.checkerframework.checker.index.qual.LessThanBottom;
import org.checkerframework.checker.index.qual.LessThanUnknown;
import org.checkerframework.checker.index.upperbound.OffsetEquation;
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.ArrayLen;
import org.checkerframework.common.value.qual.ArrayLenRange;
import org.checkerframework.common.value.qual.IntRange;
import org.checkerframework.common.value.qual.IntVal;
import org.checkerframework.dataflow.expression.FieldAccess;
import org.checkerframework.dataflow.expression.JavaExpression;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.ElementQualifierHierarchy;
import org.checkerframework.framework.type.QualifierHierarchy;
import org.checkerframework.framework.util.JavaExpressionParseUtil.JavaExpressionParseException;
import org.checkerframework.framework.util.dependenttypes.DependentTypesHelper;
import org.checkerframework.javacutil.AnnotationBuilder;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.SystemUtil;
import org.checkerframework.javacutil.TreeUtils;
/** The type factory for the Less Than Checker. */
public class LessThanAnnotatedTypeFactory extends BaseAnnotatedTypeFactoryForIndexChecker {
/** The @LessThanBottom annotation. */
private final AnnotationMirror LESS_THAN_BOTTOM =
AnnotationBuilder.fromClass(elements, LessThanBottom.class);
/** The @LessThanUnknown annotation. */
public final AnnotationMirror LESS_THAN_UNKNOWN =
AnnotationBuilder.fromClass(elements, LessThanUnknown.class);
/** The LessThan.value argument/element. */
private final ExecutableElement lessThanValueElement =
TreeUtils.getMethod(LessThan.class, "value", 0, processingEnv);
/**
* Creates a new LessThanAnnotatedTypeFactory.
*
* @param checker the type-checker associated with this type factory
*/
public LessThanAnnotatedTypeFactory(BaseTypeChecker checker) {
super(checker);
postInit();
}
/**
* Returns the Value Checker's annotated type factory.
*
* @return the Value Checker's annotated type factory
*/
public ValueAnnotatedTypeFactory getValueAnnotatedTypeFactory() {
return getTypeFactoryOfSubchecker(ValueChecker.class);
}
@Override
protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
return new LinkedHashSet<>(
Arrays.asList(LessThan.class, LessThanUnknown.class, LessThanBottom.class));
}
@Override
protected DependentTypesHelper createDependentTypesHelper() {
// Allows + or - in a @LessThan.
return new OffsetDependentTypesHelper(this);
}
@Override
protected QualifierHierarchy createQualifierHierarchy() {
return new LessThanQualifierHierarchy(this.getSupportedTypeQualifiers(), elements);
}
/** LessThanQualifierHierarchy. */
class LessThanQualifierHierarchy extends ElementQualifierHierarchy {
/**
* Creates a LessThanQualifierHierarchy from the given classes.
*
* @param qualifierClasses classes of annotations that are the qualifiers
* @param elements element utils
*/
public LessThanQualifierHierarchy(
Set<Class<? extends Annotation>> qualifierClasses, Elements elements) {
super(qualifierClasses, elements);
}
@Override
public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) {
List<String> subList = getLessThanExpressions(subAnno);
if (subList == null) {
return true;
}
List<String> superList = getLessThanExpressions(superAnno);
if (superList == null) {
return false;
}
return subList.containsAll(superList);
}
@Override
public AnnotationMirror leastUpperBound(AnnotationMirror a1, AnnotationMirror a2) {
if (isSubtype(a1, a2)) {
return a2;
} else if (isSubtype(a2, a1)) {
return a1;
}
List<String> a1List = getLessThanExpressions(a1);
List<String> a2List = getLessThanExpressions(a2);
a1List.retainAll(a2List); // intersection
return createLessThanQualifier(a1List);
}
@Override
public AnnotationMirror greatestLowerBound(AnnotationMirror a1, AnnotationMirror a2) {
if (isSubtype(a1, a2)) {
return a1;
} else if (isSubtype(a2, a1)) {
return a2;
}
List<String> a1List = getLessThanExpressions(a1);
List<String> a2List = getLessThanExpressions(a2);
SystemUtil.addWithoutDuplicates(a1List, a2List); // union
return createLessThanQualifier(a1List);
}
}
/**
* Returns true if {@code left} is less than {@code right}.
*
* @param left the first tree to compare
* @param right the second tree to compare
* @return is left less than right?
*/
public boolean isLessThan(Tree left, String right) {
AnnotatedTypeMirror leftATM = getAnnotatedType(left);
return isLessThan(leftATM.getAnnotationInHierarchy(LESS_THAN_UNKNOWN), right);
}
/**
* Returns true if {@code left} is less than {@code right}.
*
* @param left the first value to compare (an annotation)
* @param right the second value to compare (an expression)
* @return is left less than right?
*/
public boolean isLessThan(AnnotationMirror left, String right) {
List<String> expressions = getLessThanExpressions(left);
if (expressions == null) {
// `left` is @LessThanBottom
return true;
}
return expressions.contains(right);
}
/**
* Returns true if {@code smaller < bigger}.
*
* @param smaller the first value to compare
* @param bigger the second value to compare
* @return {@code smaller < bigger}, using information from the Value Checker
*/
public boolean isLessThanByValue(Tree smaller, String bigger, TreePath path) {
Long smallerValue = ValueCheckerUtils.getMinValue(smaller, getValueAnnotatedTypeFactory());
if (smallerValue == null) {
return false;
}
OffsetEquation offsetEquation = OffsetEquation.createOffsetFromJavaExpression(bigger);
if (offsetEquation.isInt()) {
// bigger is an int literal
return smallerValue < offsetEquation.getInt();
}
// If bigger is "expression + literal", then smaller < expression + literal
// can be reduced to smaller - literal < expression + literal - literal
smallerValue = smallerValue - offsetEquation.getInt();
offsetEquation =
offsetEquation.copyAdd('-', OffsetEquation.createOffsetForInt(offsetEquation.getInt()));
long minValueOfBigger = getMinValueFromString(offsetEquation.toString(), smaller, path);
return smallerValue < minValueOfBigger;
}
/**
* Returns the minimum value of {@code expression} at {@code tree}.
*
* @param expression the expression whose minimum value to retrieve
* @param tree where to determine the value
* @param path the path to {@code tree}
* @return the minimum value of {@code expression} at {@code tree}
*/
private long getMinValueFromString(String expression, Tree tree, TreePath path) {
ValueAnnotatedTypeFactory valueAtypeFactory = getValueAnnotatedTypeFactory();
JavaExpression expressionJe;
try {
expressionJe = valueAtypeFactory.parseJavaExpressionString(expression, path);
} catch (JavaExpressionParseException e) {
return Long.MIN_VALUE;
}
AnnotationMirror intRange =
valueAtypeFactory.getAnnotationFromJavaExpression(expressionJe, tree, IntRange.class);
if (intRange != null) {
return valueAtypeFactory.getRange(intRange).from;
}
AnnotationMirror intValue =
valueAtypeFactory.getAnnotationFromJavaExpression(expressionJe, tree, IntVal.class);
if (intValue != null) {
List<Long> possibleValues = valueAtypeFactory.getIntValues(intValue);
return Collections.min(possibleValues);
}
if (expressionJe instanceof FieldAccess) {
FieldAccess fieldAccess = ((FieldAccess) expressionJe);
if (fieldAccess.getReceiver().getType().getKind() == TypeKind.ARRAY) {
// array.length might not be in the store, so check for the length of the array.
AnnotationMirror arrayRange =
valueAtypeFactory.getAnnotationFromJavaExpression(
fieldAccess.getReceiver(), tree, ArrayLenRange.class);
if (arrayRange != null) {
return valueAtypeFactory.getRange(arrayRange).from;
}
AnnotationMirror arrayLen =
valueAtypeFactory.getAnnotationFromJavaExpression(expressionJe, tree, ArrayLen.class);
if (arrayLen != null) {
List<Integer> possibleValues = valueAtypeFactory.getArrayLength(arrayLen);
return Collections.min(possibleValues);
}
// Even arrays that we know nothing about must have at least zero length.
return 0;
}
}
return Long.MIN_VALUE;
}
/**
* Returns true if left is less than or equal to right.
*
* @param left the first value to compare
* @param right the second value to compare
* @return is left less than or equal to right?
*/
public boolean isLessThanOrEqual(Tree left, String right) {
AnnotatedTypeMirror leftATM = getAnnotatedType(left);
return isLessThanOrEqual(leftATM.getAnnotationInHierarchy(LESS_THAN_UNKNOWN), right);
}
/**
* Returns true if left is less than or equal to right.
*
* @param left the first value to compare
* @param right the second value to compare
* @return is left less than or equal to right?
*/
public boolean isLessThanOrEqual(AnnotationMirror left, String right) {
List<String> expressions = getLessThanExpressions(left);
if (expressions == null) {
// left is bottom so it is always less than right.
return true;
}
if (expressions.contains(right)) {
return true;
}
// {@code @LessThan("end + 1")} is equivalent to {@code @LessThanOrEqual("end")}.
for (String expression : expressions) {
if (expression.endsWith(" + 1")
&& expression.substring(0, expression.length() - 4).equals(right)) {
return true;
}
}
return false;
}
/**
* Returns a sorted, modifiable list of expressions that {@code expression} is less than. If the
* {@code expression} is annotated with {@link LessThanBottom}, null is returned.
*
* @param expression an expression
* @return expressions that {@code expression} is less than
*/
public List<String> getLessThanExpressions(ExpressionTree expression) {
AnnotatedTypeMirror annotatedTypeMirror = getAnnotatedType(expression);
return getLessThanExpressions(annotatedTypeMirror.getAnnotationInHierarchy(LESS_THAN_UNKNOWN));
}
/**
* Creates a less than qualifier given the expressions.
*
* <p>If expressions is null, {@link LessThanBottom} is returned. If expressions is empty, {@link
* LessThanUnknown} is returned. Otherwise, {@code @LessThan(expressions)} is returned.
*
* @param expressions a list of expressions
* @return a @LessThan qualifier with the given arguments
*/
public AnnotationMirror createLessThanQualifier(List<String> expressions) {
if (expressions == null) {
return LESS_THAN_BOTTOM;
} else if (expressions.isEmpty()) {
return LESS_THAN_UNKNOWN;
} else {
AnnotationBuilder builder = new AnnotationBuilder(processingEnv, LessThan.class);
builder.setValue("value", expressions);
return builder.build();
}
}
/** Returns {@code @LessThan(expression)}. */
public AnnotationMirror createLessThanQualifier(String expression) {
return createLessThanQualifier(Collections.singletonList(expression));
}
/**
* If the annotation is LessThan, returns a list of expressions in the annotation. If the
* annotation is {@link LessThanBottom}, returns null. If the annotation is {@link
* LessThanUnknown}, returns the empty list.
*
* @param annotation an annotation from the same hierarchy as LessThan
* @return the list of expressions in the annotation
*/
public List<String> getLessThanExpressions(AnnotationMirror annotation) {
if (AnnotationUtils.areSameByName(
annotation, "org.checkerframework.checker.index.qual.LessThanBottom")) {
return null;
} else if (AnnotationUtils.areSameByName(
annotation, "org.checkerframework.checker.index.qual.LessThanUnknown")) {
return Collections.emptyList();
} else {
// The annotation is @LessThan.
return AnnotationUtils.getElementValueArray(annotation, lessThanValueElement, String.class);
}
}
}