org.checkerframework.common.value.ValueCheckerUtils Maven / Gradle / Ivy
Show all versions of checker Show documentation
package org.checkerframework.common.value;
import com.sun.source.tree.Tree;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.checker.mustcall.qual.MustCallUnknown;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.common.value.qual.IntRange;
import org.checkerframework.common.value.qual.IntVal;
import org.checkerframework.common.value.qual.StringVal;
import org.checkerframework.common.value.util.NumberUtils;
import org.checkerframework.common.value.util.Range;
import org.checkerframework.dataflow.expression.JavaExpression;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.TypeSystemError;
import org.checkerframework.javacutil.TypesUtils;
import org.plumelib.util.CollectionsPlume;
/** Utility methods for the Value Checker. */
public class ValueCheckerUtils {
/** Do not instantiate. */
private ValueCheckerUtils() {
throw new TypeSystemError("do not instantiate");
}
/**
* Get a list of the values of an annotation, and then cast the values to a given type.
*
* @param anno the annotation that contains values
* @param castTo the type that is casted to
* @param atypeFactory the type factory
* @return a list of values after the casting
*/
public static List> getValuesCastedToType(
AnnotationMirror anno, TypeMirror castTo, ValueAnnotatedTypeFactory atypeFactory) {
return getValuesCastedToType(anno, castTo, false, atypeFactory);
}
/**
* Get a list of the values of an annotation, and then cast the values to a given type.
*
* @param anno the annotation that contains values
* @param castTo the unannotated type that is casted to
* @param isUnsigned true if the type being casted to is unsigned
* @param atypeFactory the type factory
* @return a list of values after the casting
*/
public static List> getValuesCastedToType(
AnnotationMirror anno,
TypeMirror castTo,
boolean isUnsigned,
ValueAnnotatedTypeFactory atypeFactory) {
Class> castType = TypesUtils.getClassFromType(castTo);
List> values;
switch (AnnotationUtils.annotationName(anno)) {
case ValueAnnotatedTypeFactory.DOUBLEVAL_NAME:
values = convertDoubleVal(anno, castType, castTo, atypeFactory);
break;
case ValueAnnotatedTypeFactory.INTVAL_NAME:
List longs = atypeFactory.getIntValues(anno);
values = convertIntVal(longs, castType, castTo, isUnsigned);
break;
case ValueAnnotatedTypeFactory.INTRANGE_NAME:
Range range = atypeFactory.getRange(anno);
List rangeValues = getValuesFromRange(range, Long.class);
values = convertIntVal(rangeValues, castType, castTo, isUnsigned);
break;
case ValueAnnotatedTypeFactory.STRINGVAL_NAME:
values = convertStringVal(anno, castType, atypeFactory);
break;
case ValueAnnotatedTypeFactory.BOOLVAL_NAME:
values = convertBoolVal(anno, castType, atypeFactory);
break;
case ValueAnnotatedTypeFactory.BOTTOMVAL_NAME:
case ValueAnnotatedTypeFactory.ARRAYLEN_NAME:
values = Collections.emptyList();
break;
default:
values = null;
}
return values;
}
/** Get the minimum and maximum of a list and return a range bounded by them. */
public static @Nullable Range getRangeFromValues(List extends Number> values) {
if (values == null) {
return null;
} else if (values.isEmpty()) {
return Range.NOTHING;
}
return Range.create(values);
}
/**
* Converts a long value to a boxed numeric type.
*
* @param value a long value
* @param expectedType the boxed numeric type of the result
* @return {@code value} converted to {@code expectedType} using standard conversion rules
*/
private static T convertLongToType(long value, Class expectedType) {
Object convertedValue;
if (expectedType == Integer.class) {
convertedValue = (int) value;
} else if (expectedType == Short.class) {
convertedValue = (short) value;
} else if (expectedType == Byte.class) {
convertedValue = (byte) value;
} else if (expectedType == Long.class) {
convertedValue = value;
} else if (expectedType == Double.class) {
convertedValue = (double) value;
} else if (expectedType == Float.class) {
convertedValue = (float) value;
} else if (expectedType == Character.class) {
convertedValue = (char) value;
} else {
throw new UnsupportedOperationException(
"ValueCheckerUtils: unexpected class: " + expectedType);
}
return expectedType.cast(convertedValue);
}
/**
* Get all possible values from the given type and cast them into a boxed primitive type. Returns
* null if the list would have length greater than {@link ValueAnnotatedTypeFactory#MAX_VALUES}.
*
* {@code expectedType} must be a boxed type, not a primitive type, because primitive types
* cannot be stored in a list.
*
* @param the type of the values to obtain
* @param range the given range
* @param expectedType the expected type
* @return a list of all the values in the range, or null if there would be more than {@link
* ValueAnnotatedTypeFactory#MAX_VALUES}
*/
public static @Nullable List getValuesFromRange(
@Nullable Range range, Class expectedType) {
if (range == null || range.isWiderThan(ValueAnnotatedTypeFactory.MAX_VALUES)) {
return null;
}
if (range.isNothing()) {
return Collections.emptyList();
}
// The subtraction does not overflow, because the width has already been checked, so the
// bound difference is less than ValueAnnotatedTypeFactory.MAX_VALUES.
long boundDifference = range.to - range.from;
// Each value is computed as a sum of the first value and an offset within the range,
// to avoid having range.to as an upper bound of the loop. range.to can be Long.MAX_VALUE,
// in which case a comparison value <= range.to would be always true.
// boundDifference is always much smaller than Long.MAX_VALUE
List values = new ArrayList<>((int) boundDifference + 1);
for (long offset = 0; offset <= boundDifference; offset++) {
long value = range.from + offset;
values.add(convertLongToType(value, expectedType));
}
return values;
}
/**
* Converts a list of objects to a list of their string representations.
*
* @param origValues the objects to format
* @return a list of the formatted objects
*/
private static @Nullable List> convertToStringVal(
List extends @MustCallUnknown Object> origValues) {
if (origValues == null) {
return null;
}
return CollectionsPlume.mapList(Object::toString, origValues);
}
/**
* Convert the {@code value} argument/element of a @BoolVal annotation into a list.
*
* @param anno a @BoolVal annotation
* @param newClass if String.class, the returned list is a {@code List}
* @param atypeFactory the type factory, used for obtaining fields/elements from annotations
* @return the {@code value} of a @BoolVal annotation, as a {@code List} or a {@code
* List}
*/
private static List> convertBoolVal(
AnnotationMirror anno, Class> newClass, ValueAnnotatedTypeFactory atypeFactory) {
List bools =
AnnotationUtils.getElementValueArray(anno, atypeFactory.boolValValueElement, Boolean.class);
if (newClass == String.class) {
return convertToStringVal(bools);
}
return bools;
}
/**
* Convert the {@code value} argument/element of a {@code @StringVal} annotation into a list.
*
* @param anno a {@code @StringVal} annotation
* @param newClass if char[].class, the returned list is a {@code List}
* @param atypeFactory the type factory, used for obtaining fields/elements from annotations
* @return the {@code value} of a {@code @StringVal} annotation, as a {@code List} or a
* {@code List}
*/
private static List> convertStringVal(
AnnotationMirror anno, Class> newClass, ValueAnnotatedTypeFactory atypeFactory) {
List strings = atypeFactory.getStringValues(anno);
if (newClass == char[].class) {
return CollectionsPlume.mapList(String::toCharArray, strings);
}
return strings;
}
/**
* Convert a list of longs to a given type
*
* @param longs the integral values to convert
* @param newClass determines the type of the result
* @param newType the type to which to cast, if newClass is numeric
* @param isUnsigned if true, treat {@code newType} as unsigned
* @return the {@code value} of a {@code @IntVal} annotation, as a {@code List} or a
* {@code List}
*/
private static @Nullable List> convertIntVal(
List longs, Class> newClass, TypeMirror newType, boolean isUnsigned) {
if (longs == null) {
return null;
}
if (newClass == String.class) {
return convertToStringVal(longs);
} else if (newClass == Character.class || newClass == char.class) {
return CollectionsPlume.mapList((Long l) -> (char) l.longValue(), longs);
} else if (newClass == Boolean.class) {
throw new UnsupportedOperationException(
"ValueAnnotatedTypeFactory: can't convert integral type to boolean");
}
return NumberUtils.castNumbers(newType, isUnsigned, longs);
}
/**
* Convert the {@code value} argument/element of a @StringVal annotation into a list.
*
* @param anno a {@code @DoubleVal} annotation
* @param newClass the component type for the returned list
* @param newType the component type for the returned list
* @param atypeFactory the type factory, used for obtaining fields/elements from annotations
* @return the {@code value} of a {@code @DoubleVal} annotation
*/
private static @Nullable List> convertDoubleVal(
AnnotationMirror anno,
Class> newClass,
TypeMirror newType,
ValueAnnotatedTypeFactory atypeFactory) {
List doubles = atypeFactory.getDoubleValues(anno);
if (doubles == null) {
return null;
}
if (newClass == String.class) {
return convertToStringVal(doubles);
} else if (newClass == Character.class || newClass == char.class) {
return CollectionsPlume.mapList((Double l) -> (char) l.doubleValue(), doubles);
} else if (newClass == Boolean.class) {
throw new UnsupportedOperationException(
"ValueAnnotatedTypeFactory: can't convert double to boolean");
}
return NumberUtils.castNumbers(newType, doubles);
}
/**
* Gets a list of lengths for a list of string values.
*
* @param values list of string values
* @return list of unique lengths of strings in {@code values}
*/
public static List getLengthsForStringValues(List values) {
List lengths = CollectionsPlume.mapList(String::length, values);
return CollectionsPlume.withoutDuplicatesSorted(lengths);
}
/**
* Returns a range representing the possible integral values represented by the passed {@code
* AnnotatedTypeMirror}. If the passed {@code AnnotatedTypeMirror} does not contain an {@code
* IntRange} annotation or an {@code IntVal} annotation, returns null.
*/
public static @Nullable Range getPossibleValues(
AnnotatedTypeMirror valueType, ValueAnnotatedTypeFactory valueAnnotatedTypeFactory) {
if (valueAnnotatedTypeFactory.isIntRange(valueType.getPrimaryAnnotations())) {
return valueAnnotatedTypeFactory.getRange(valueType.getPrimaryAnnotation(IntRange.class));
} else {
List values =
valueAnnotatedTypeFactory.getIntValues(valueType.getPrimaryAnnotation(IntVal.class));
if (values != null) {
return Range.create(values);
} else {
return null;
}
}
}
/**
* Either returns the exact value of the given tree according to the Constant Value Checker, or
* null if the exact value is not known. This method should only be used by clients who need
* exactly one value -- such as the LBC's binary operator rules -- and not by those that need to
* know whether a valueType belongs to a particular qualifier.
*/
public static @Nullable Long getExactValue(Tree tree, ValueAnnotatedTypeFactory factory) {
AnnotatedTypeMirror valueType = factory.getAnnotatedType(tree);
Range possibleValues = getPossibleValues(valueType, factory);
if (possibleValues != null && possibleValues.from == possibleValues.to) {
return possibleValues.from;
} else {
return null;
}
}
/**
* Returns the exact value of an annotated element according to the Constant Value Checker, or
* null if the exact value is not known.
*
* @param element the element to get the exact value from
* @param factory a ValueAnnotatedTypeFactory used for annotation accessing
* @return the exact value of the element if it is constant, or null otherwise
*/
public static @Nullable Long getExactValue(Element element, ValueAnnotatedTypeFactory factory) {
AnnotatedTypeMirror valueType = factory.getAnnotatedType(element);
Range possibleValues = getPossibleValues(valueType, factory);
if (possibleValues != null && possibleValues.from == possibleValues.to) {
return possibleValues.from;
} else {
return null;
}
}
/**
* Either returns the exact string value of the given tree according to the Constant Value
* Checker, or null if the exact value is not known. This method should only be used by clients
* who need exactly one value and not by those that need to know whether a valueType belongs to a
* particular qualifier.
*/
public static @Nullable String getExactStringValue(Tree tree, ValueAnnotatedTypeFactory factory) {
AnnotatedTypeMirror valueType = factory.getAnnotatedType(tree);
if (valueType.hasPrimaryAnnotation(StringVal.class)) {
AnnotationMirror valueAnno = valueType.getPrimaryAnnotation(StringVal.class);
List possibleValues =
AnnotationUtils.getElementValueArray(
valueAnno, factory.stringValValueElement, String.class);
if (possibleValues.size() == 1) {
return possibleValues.get(0);
}
}
return null;
}
/**
* Finds the minimum value in a Value Checker type. If there is no information (such as when the
* list of possible values is empty or null), returns null. Otherwise, returns the smallest value
* in the list of possible values.
*/
public static @Nullable Long getMinValue(Tree tree, ValueAnnotatedTypeFactory factory) {
AnnotatedTypeMirror valueType = factory.getAnnotatedType(tree);
Range possibleValues = getPossibleValues(valueType, factory);
if (possibleValues != null) {
return possibleValues.from;
} else {
return null;
}
}
/**
* Finds the maximum value in a Value Checker type. If there is no information (such as when the
* list of possible values is empty or null), returns null. Otherwise, returns the smallest value
* in the list of possible values.
*/
public static @Nullable Long getMaxValue(Tree tree, ValueAnnotatedTypeFactory factory) {
AnnotatedTypeMirror valueType = factory.getAnnotatedType(tree);
Range possibleValues = getPossibleValues(valueType, factory);
if (possibleValues != null) {
return possibleValues.to;
} else {
return null;
}
}
/**
* Looks up the minlen of a member select tree. The tree must be an access to a sequence length.
*/
public static @Nullable Integer getMinLenFromTree(Tree tree, ValueAnnotatedTypeFactory valueATF) {
AnnotatedTypeMirror minLenType = valueATF.getAnnotatedType(tree);
Long min = valueATF.getMinimumIntegralValue(minLenType);
if (min == null) {
return null;
}
if (min < 0 || min > Integer.MAX_VALUE) {
min = 0L;
}
return min.intValue();
}
/**
* Queries the Value Checker to determine if there is a known minimum length for the array
* represented by {@code tree}. If not, returns 0.
*/
public static int getMinLen(Tree tree, ValueAnnotatedTypeFactory valueAnnotatedTypeFactory) {
AnnotatedTypeMirror minLenType = valueAnnotatedTypeFactory.getAnnotatedType(tree);
return valueAnnotatedTypeFactory.getMinLenValue(minLenType);
}
/**
* Optimize the given JavaExpression. See {@link JavaExpressionOptimizer} for more details.
*
* @param je the expression to optimize
* @param factory the annotated type factory
* @return an optimized version of the argument
*/
public static JavaExpression optimize(JavaExpression je, AnnotatedTypeFactory factory) {
ValueAnnotatedTypeFactory vatf =
((GenericAnnotatedTypeFactory, ?, ?, ?>) factory)
.getTypeFactoryOfSubcheckerOrNull(ValueChecker.class);
return new JavaExpressionOptimizer(vatf == null ? factory : vatf).convert(je);
}
}