| 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.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.BugInCF; |
| 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 BugInCF("do not instantiate"); |
| } |
| |
| /** |
| * Get a list of values of annotation, and then cast them 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) { |
| 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<Long> longs = atypeFactory.getIntValues(anno); |
| values = convertIntVal(longs, castType, castTo); |
| break; |
| case ValueAnnotatedTypeFactory.INTRANGE_NAME: |
| Range range = atypeFactory.getRange(anno); |
| List<Long> rangeValues = getValuesFromRange(range, Long.class); |
| values = convertIntVal(rangeValues, castType, castTo); |
| 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 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> T convertLongToType(long value, Class<T> 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. |
| * |
| * <p>{@code expectedType} must be a boxed type, not a primitive type, because primitive types |
| * cannot be stored in a list. |
| * |
| * @param range the given range |
| * @param expectedType the expected type |
| * @return a list of all the values in the range |
| */ |
| public static <T> List<T> getValuesFromRange(Range range, Class<T> 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<T> 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; |
| } |
| |
| private static List<?> convertToStringVal(List<?> 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<String>} |
| * @param atypeFactory the type factory, used for obtaining fields/elements from annotations |
| * @return the {@code value} of a @BoolVal annotation, as a {@code List<Boolean>} or a {@code |
| * List<String>} |
| */ |
| private static List<?> convertBoolVal( |
| AnnotationMirror anno, Class<?> newClass, ValueAnnotatedTypeFactory atypeFactory) { |
| List<Boolean> 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<char[]>} |
| * @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<String>} or a |
| * {@code List<char[]>} |
| */ |
| private static List<?> convertStringVal( |
| AnnotationMirror anno, Class<?> newClass, ValueAnnotatedTypeFactory atypeFactory) { |
| List<String> strings = atypeFactory.getStringValues(anno); |
| if (newClass == char[].class) { |
| return CollectionsPlume.mapList(String::toCharArray, strings); |
| } |
| return strings; |
| } |
| |
| private static List<?> convertIntVal(List<Long> longs, Class<?> newClass, TypeMirror newType) { |
| 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 int to boolean"); |
| } |
| return NumberUtils.castNumbers(newType, 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 List<?> convertDoubleVal( |
| AnnotationMirror anno, |
| Class<?> newClass, |
| TypeMirror newType, |
| ValueAnnotatedTypeFactory atypeFactory) { |
| List<Double> 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<Integer> getLengthsForStringValues(List<String> values) { |
| List<Integer> lengths = CollectionsPlume.mapList(String::length, values); |
| return CollectionsPlume.withoutDuplicates(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 Range getPossibleValues( |
| AnnotatedTypeMirror valueType, ValueAnnotatedTypeFactory valueAnnotatedTypeFactory) { |
| if (valueAnnotatedTypeFactory.isIntRange(valueType.getAnnotations())) { |
| return valueAnnotatedTypeFactory.getRange(valueType.getAnnotation(IntRange.class)); |
| } else { |
| List<Long> values = |
| valueAnnotatedTypeFactory.getIntValues(valueType.getAnnotation(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 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 ValueAnnotatedTypeFactory used for annotation accessing |
| * @return the exact value of the element if it is constant, or null otherwise |
| */ |
| public static 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 String getExactStringValue(Tree tree, ValueAnnotatedTypeFactory factory) { |
| AnnotatedTypeMirror valueType = factory.getAnnotatedType(tree); |
| if (valueType.hasAnnotation(StringVal.class)) { |
| AnnotationMirror valueAnno = valueType.getAnnotation(StringVal.class); |
| List<String> 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 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 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 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) |
| .getTypeFactoryOfSubchecker(ValueChecker.class); |
| return new JavaExpressionOptimizer(vatf == null ? factory : vatf).convert(je); |
| } |
| } |