package org.checkerframework.common.value; 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.NewArrayTree; import com.sun.source.tree.NewClassTree; import com.sun.source.tree.Tree; import com.sun.source.tree.Tree.Kind; import com.sun.source.tree.TypeCastTree; import com.sun.source.util.TreePath; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.TreeSet; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.value.qual.ArrayLen; import org.checkerframework.common.value.qual.ArrayLenRange; import org.checkerframework.common.value.qual.BoolVal; import org.checkerframework.common.value.qual.BottomVal; import org.checkerframework.common.value.qual.DoubleVal; import org.checkerframework.common.value.qual.IntRange; import org.checkerframework.common.value.qual.IntVal; import org.checkerframework.common.value.qual.MinLen; import org.checkerframework.common.value.qual.MinLenFieldInvariant; import org.checkerframework.common.value.qual.PolyValue; import org.checkerframework.common.value.qual.StaticallyExecutable; import org.checkerframework.common.value.qual.StringVal; import org.checkerframework.common.value.qual.UnknownVal; import org.checkerframework.common.value.util.NumberUtils; import org.checkerframework.common.value.util.Range; import org.checkerframework.framework.flow.CFAbstractAnalysis; import org.checkerframework.framework.flow.CFStore; import org.checkerframework.framework.flow.CFTransfer; import org.checkerframework.framework.flow.CFValue; import org.checkerframework.framework.qual.PolyAll; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; import org.checkerframework.framework.type.QualifierHierarchy; 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.framework.type.typeannotator.ListTypeAnnotator; import org.checkerframework.framework.type.typeannotator.TypeAnnotator; import org.checkerframework.framework.util.AnnotationBuilder; import org.checkerframework.framework.util.FieldInvariants; import org.checkerframework.framework.util.FlowExpressionParseUtil.FlowExpressionParseException; import org.checkerframework.framework.util.MultiGraphQualifierHierarchy; import org.checkerframework.framework.util.MultiGraphQualifierHierarchy.MultiGraphFactory; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.InternalUtils; import org.checkerframework.javacutil.Pair; import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypesUtils; /** * AnnotatedTypeFactory for the Value type system. * * @author plvines * @author smillst */ public class ValueAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { /** The maximum number of values allowed in an annotation's array */ protected static final int MAX_VALUES = 10; /** * The domain of the Constant Value Checker: the types for which it estimates possible values. */ protected static final Set<String> coveredClassStrings; /** The top type for this hierarchy. */ protected final AnnotationMirror UNKNOWNVAL; /** The bottom type for this hierarchy. */ protected final AnnotationMirror BOTTOMVAL; /** The canonical @{@link PolyValue} annotation. */ public final AnnotationMirror POLY = AnnotationUtils.fromClass(elements, PolyValue.class); /** Should this type factory report warnings? */ private final boolean reportEvalWarnings; /** Helper class that evaluates statically executable methods, constructors, and fields. */ private final ReflectiveEvaluator evaluator; static { Set<String> backingSet = new HashSet<String>(18); backingSet.add("int"); backingSet.add("java.lang.Integer"); backingSet.add("double"); backingSet.add("java.lang.Double"); backingSet.add("byte"); backingSet.add("java.lang.Byte"); backingSet.add("java.lang.String"); backingSet.add("char"); backingSet.add("java.lang.Character"); backingSet.add("float"); backingSet.add("java.lang.Float"); backingSet.add("boolean"); backingSet.add("java.lang.Boolean"); backingSet.add("long"); backingSet.add("java.lang.Long"); backingSet.add("short"); backingSet.add("java.lang.Short"); backingSet.add("byte[]"); coveredClassStrings = Collections.unmodifiableSet(backingSet); } public ValueAnnotatedTypeFactory(BaseTypeChecker checker) { super(checker); BOTTOMVAL = AnnotationUtils.fromClass(elements, BottomVal.class); UNKNOWNVAL = AnnotationUtils.fromClass(elements, UnknownVal.class); reportEvalWarnings = checker.hasOption(ValueChecker.REPORT_EVAL_WARNS); evaluator = new ReflectiveEvaluator(checker, this, reportEvalWarnings); // The actual ArrayLenRange is created by // {@link ValueAnnotatedTypeFactory#aliasedAnnotation(AnnotationMirror)}; // this line just registers the alias. The BottomVal is never used. addAliasedAnnotation(MinLen.class, BOTTOMVAL); if (this.getClass().equals(ValueAnnotatedTypeFactory.class)) { this.postInit(); } } @Override public AnnotationMirror aliasedAnnotation(AnnotationMirror anno) { if (AnnotationUtils.areSameByClass(anno, android.support.annotation.IntRange.class)) { Range range = getRange(anno); return createIntRangeAnnotation(range); } if (AnnotationUtils.areSameByClass(anno, MinLen.class)) { Integer from = getMinLenValue(anno); if (from != null && from >= 0) { return createArrayLenRangeAnnotation(from, Integer.MAX_VALUE); } else { return createArrayLenRangeAnnotation(0, Integer.MAX_VALUE); } } return super.aliasedAnnotation(anno); } @Override protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() { // Because the Value Checker includes its own alias annotations, // the qualifiers have to be explicitly defined. return new LinkedHashSet<>( Arrays.asList( ArrayLen.class, ArrayLenRange.class, IntVal.class, IntRange.class, BoolVal.class, StringVal.class, DoubleVal.class, BottomVal.class, UnknownVal.class, PolyValue.class, PolyAll.class)); } @Override public CFTransfer createFlowTransferFunction( CFAbstractAnalysis<CFValue, CFStore, CFTransfer> analysis) { return new ValueTransfer(analysis); } @Override public QualifierHierarchy createQualifierHierarchy(MultiGraphFactory factory) { return new ValueQualifierHierarchy(factory); } @Override protected TypeAnnotator createTypeAnnotator() { return new ListTypeAnnotator(new ValueTypeAnnotator(this), super.createTypeAnnotator()); } @Override public FieldInvariants getFieldInvariants(TypeElement element) { AnnotationMirror fieldInvarAnno = getDeclAnnotation(element, MinLenFieldInvariant.class); if (fieldInvarAnno == null) { return null; } List<String> fields = AnnotationUtils.getElementValueArray(fieldInvarAnno, "field", String.class, true); List<Integer> minlens = AnnotationUtils.getElementValueArray(fieldInvarAnno, "minLen", Integer.class, true); List<AnnotationMirror> qualifiers = new ArrayList<>(); for (Integer minlen : minlens) { qualifiers.add(createArrayLenRangeAnnotation(minlen, Integer.MAX_VALUE)); } FieldInvariants superInvariants = super.getFieldInvariants(element); return new FieldInvariants(superInvariants, fields, qualifiers); } @Override protected Set<Class<? extends Annotation>> getFieldInvariantDeclarationAnnotations() { // include FieldInvariant so that @MinLenBottom can be used. Set<Class<? extends Annotation>> set = new HashSet<>(super.getFieldInvariantDeclarationAnnotations()); set.add(MinLenFieldInvariant.class); return set; } /** * Creates array length annotations for the result of the Enum.values() method, which is the * number of possible values of the enum. */ @Override public Pair<AnnotatedTypeMirror.AnnotatedExecutableType, List<AnnotatedTypeMirror>> methodFromUse( ExpressionTree tree, ExecutableElement methodElt, AnnotatedTypeMirror receiverType) { Pair<AnnotatedTypeMirror.AnnotatedExecutableType, List<AnnotatedTypeMirror>> superPair = super.methodFromUse(tree, methodElt, receiverType); if (ElementUtils.matchesElement(methodElt, "values") && methodElt.getEnclosingElement().getKind() == ElementKind.ENUM && ElementUtils.isStatic(methodElt)) { int count = 0; List<? extends Element> l = methodElt.getEnclosingElement().getEnclosedElements(); for (Element el : l) { if (el.getKind() == ElementKind.ENUM_CONSTANT) { count++; } } AnnotationMirror am = createArrayLenAnnotation(Collections.singletonList(count)); superPair.first.getReturnType().replaceAnnotation(am); } return superPair; } /** * Performs pre-processing on annotations written by users, replacing illegal annotations by * legal ones. */ private class ValueTypeAnnotator extends TypeAnnotator { private ValueTypeAnnotator(AnnotatedTypeFactory atypeFactory) { super(atypeFactory); } @Override protected Void scan(AnnotatedTypeMirror type, Void aVoid) { if (type != null) { replaceWithNewAnnoInSpecialCases(type); } return super.scan(type, aVoid); } /** * This method performs pre-processing on annotations written by users. * * <p>If any *Val annotation has > MAX_VALUES number of values provided, replaces the * annotation by @IntRange for integral types, @ArrayLenRange for arrays, and @UnknownVal * for all other types. Works together with {@link * org.checkerframework.common.value.ValueVisitor#visitAnnotation(com.sun.source.tree.AnnotationTree, * Void)} which issues warnings to users in these cases. * * <p>If any @IntRange or @ArrayLenRange annotation has incorrect parameters, e.g. the value * "from" is greater than the value "to", replaces the annotation by @BOTTOMVAL. The {@link * org.checkerframework.common.value.ValueVisitor#visitAnnotation(com.sun.source.tree.AnnotationTree, * Void)} would raise an error to users in this case. * * <p>If any @ArrayLen annotation has a negative number, replaces the annotation * by @BOTTOMVAL. The {@link * org.checkerframework.common.value.ValueVisitor#visitAnnotation(com.sun.source.tree.AnnotationTree, * Void)} would raise an error to users in this case. */ private void replaceWithNewAnnoInSpecialCases(AnnotatedTypeMirror atm) { AnnotationMirror anno = atm.getAnnotationInHierarchy(UNKNOWNVAL); if (anno == null) { return; } if (anno != null && anno.getElementValues().size() > 0) { if (AnnotationUtils.areSameByClass(anno, IntVal.class)) { List<Long> values = AnnotationUtils.getElementValueArray(anno, "value", Long.class, true); if (values.size() > MAX_VALUES) { long annoMinVal = Collections.min(values); long annoMaxVal = Collections.max(values); atm.replaceAnnotation( createIntRangeAnnotation(new Range(annoMinVal, annoMaxVal))); } } else if (AnnotationUtils.areSameByClass(anno, ArrayLen.class)) { List<Integer> values = AnnotationUtils.getElementValueArray( anno, "value", Integer.class, true); if (values.isEmpty()) { atm.replaceAnnotation(BOTTOMVAL); } else if (Collections.min(values) < 0) { atm.replaceAnnotation(BOTTOMVAL); } else if (values.size() > MAX_VALUES) { long annoMinVal = Collections.min(values); long annoMaxVal = Collections.max(values); atm.replaceAnnotation( createArrayLenRangeAnnotation(new Range(annoMinVal, annoMaxVal))); } } else if (AnnotationUtils.areSameByClass(anno, IntRange.class)) { long from = AnnotationUtils.getElementValue(anno, "from", Long.class, true); long to = AnnotationUtils.getElementValue(anno, "to", Long.class, true); if (from > to) { atm.replaceAnnotation(BOTTOMVAL); } } else if (AnnotationUtils.areSameByClass(anno, ArrayLenRange.class)) { int from = AnnotationUtils.getElementValue(anno, "from", Integer.class, true); int to = AnnotationUtils.getElementValue(anno, "to", Integer.class, true); if (from > to || from < 0) { atm.replaceAnnotation(BOTTOMVAL); } } else { // In here the annotation is @*Val where (*) is not Int but other types (String, Double, etc). // Therefore we extract its values in a generic way to check its size. List<Object> values = AnnotationUtils.getElementValueArray( anno, "value", Object.class, false); if (values.size() > MAX_VALUES) { atm.replaceAnnotation(UNKNOWNVAL); } } } } } /** The qualifier hierarchy for the Value type system */ private final class ValueQualifierHierarchy extends MultiGraphQualifierHierarchy { /** @param factory MultiGraphFactory to use to construct this */ public ValueQualifierHierarchy(MultiGraphQualifierHierarchy.MultiGraphFactory factory) { super(factory); } @Override public AnnotationMirror greatestLowerBound(AnnotationMirror a1, AnnotationMirror a2) { if (isSubtype(a1, a2)) { return a1; } else if (isSubtype(a2, a1)) { return a2; } else { // Simply return BOTTOMVAL if not related. Refine this if discover more use cases // that need a more precision GLB. return BOTTOMVAL; } } @Override public boolean implementsWidening() { return true; } @Override public AnnotationMirror widenUpperBound(AnnotationMirror a1, AnnotationMirror a2) { AnnotationMirror lub = leastUpperBound(a1, a2); if (AnnotationUtils.areSameByClass(lub, IntRange.class)) { Range range = getRange(lub); if (range.isWithin(Byte.MIN_VALUE, Byte.MAX_VALUE)) { return createIntRangeAnnotation(Range.BYTE_EVERYTHING); } else if (range.isWithin(Short.MIN_VALUE, Short.MAX_VALUE)) { return createIntRangeAnnotation(Range.SHORT_EVERYTHING); } else if (range.isWithin(Integer.MIN_VALUE, Integer.MAX_VALUE)) { return createIntRangeAnnotation(Range.INT_EVERYTHING); } else { return UNKNOWNVAL; } } return lub; } /** * Determines the least upper bound of a1 and a2, which contains the union of their sets of * possible values. * * @return the least upper bound of a1 and a2 */ @Override public AnnotationMirror leastUpperBound(AnnotationMirror a1, AnnotationMirror a2) { if (!AnnotationUtils.areSameIgnoringValues( getTopAnnotation(a1), getTopAnnotation(a2))) { // The annotations are in different hierarchies return null; } if (isSubtype(a1, a2)) { return a2; } else if (isSubtype(a2, a1)) { return a1; } if (AnnotationUtils.areSameIgnoringValues(a1, a2)) { // If both are the same type, determine the type and merge if (AnnotationUtils.areSameByClass(a1, IntRange.class)) { // special handling for IntRange Range range1 = getRange(a1); Range range2 = getRange(a2); return createIntRangeAnnotation(range1.union(range2)); } else if (AnnotationUtils.areSameByClass(a1, ArrayLenRange.class)) { // special handling for ArrayLenRange Range range1 = getRange(a1); Range range2 = getRange(a2); return createArrayLenRangeAnnotation(range1.union(range2)); } else if (AnnotationUtils.areSameByClass(a1, IntVal.class)) { List<Long> a1Values = AnnotationUtils.getElementValueArray(a1, "value", Long.class, true); List<Long> a2Values = AnnotationUtils.getElementValueArray(a2, "value", Long.class, true); List<Long> newValues = new ArrayList<>(); newValues.addAll(a1Values); newValues.addAll(a2Values); return createIntValAnnotation(newValues); } else if (AnnotationUtils.areSameByClass(a1, ArrayLen.class)) { List<Integer> a1Values = AnnotationUtils.getElementValueArray(a1, "value", Integer.class, true); List<Integer> a2Values = AnnotationUtils.getElementValueArray(a2, "value", Integer.class, true); List<Integer> newValues = new ArrayList<>(); newValues.addAll(a1Values); newValues.addAll(a2Values); return createArrayLenAnnotation(newValues); } else { List<Object> a1Values = AnnotationUtils.getElementValueArray(a1, "value", Object.class, true); List<Object> a2Values = AnnotationUtils.getElementValueArray(a2, "value", Object.class, true); TreeSet<Object> newValues = new TreeSet<>(); newValues.addAll(a1Values); newValues.addAll(a2Values); if (newValues.size() == 0) { return BOTTOMVAL; } if (newValues.size() > MAX_VALUES) { return UNKNOWNVAL; } AnnotationBuilder builder = new AnnotationBuilder(processingEnv, a1.getAnnotationType().toString()); List<Object> valuesList = new ArrayList<>(newValues); builder.setValue("value", valuesList); return builder.build(); } } // Special handling for dealing with the lub of an ArrayLenRange and an ArrayLen. AnnotationMirror arrayLenAnno = null; AnnotationMirror arrayLenRangeAnno = null; if (AnnotationUtils.areSameByClass(a1, ArrayLen.class)) { arrayLenAnno = a1; } else if (AnnotationUtils.areSameByClass(a2, ArrayLen.class)) { arrayLenAnno = a2; } if (AnnotationUtils.areSameByClass(a1, ArrayLenRange.class)) { arrayLenRangeAnno = a1; } else if (AnnotationUtils.areSameByClass(a2, ArrayLenRange.class)) { arrayLenRangeAnno = a2; } if (arrayLenAnno != null && arrayLenRangeAnno != null) { return leastUpperBound( arrayLenRangeAnno, convertArrayLenToArrayLenRange(arrayLenAnno)); } // Annotations are both in the same hierarchy, but they are not the same. // If a1 and a2 are not the same type of *Value annotation, they may still be mergeable // because some values can be implicitly cast as others. For example, if a1 and a2 are // both in {DoubleVal, IntVal} then they will be converted upwards: IntVal -> DoubleVal // to arrive at a common annotation type. // Each of these variables is an annotation of the given type, or is null if neither of // the arguments to leastUpperBound is of the given types. AnnotationMirror intValAnno = null; AnnotationMirror intRangeAnno = null; AnnotationMirror doubleValAnno = null; if (AnnotationUtils.areSameByClass(a1, IntVal.class)) { intValAnno = a1; } else if (AnnotationUtils.areSameByClass(a2, IntVal.class)) { intValAnno = a2; } if (AnnotationUtils.areSameByClass(a1, DoubleVal.class)) { doubleValAnno = a1; } else if (AnnotationUtils.areSameByClass(a2, DoubleVal.class)) { doubleValAnno = a2; } if (AnnotationUtils.areSameByClass(a1, IntRange.class)) { intRangeAnno = a1; } else if (AnnotationUtils.areSameByClass(a2, IntRange.class)) { intRangeAnno = a2; } if (doubleValAnno != null) { if (intRangeAnno != null) { intValAnno = convertIntRangeToIntVal(intRangeAnno); intRangeAnno = null; if (intValAnno == UNKNOWNVAL) { intValAnno = null; } } if (intValAnno != null) { // Convert intValAnno to a @DoubleVal AnnotationMirror AnnotationMirror doubleValAnno2 = convertIntValToDoubleVal(intValAnno); return leastUpperBound(doubleValAnno, doubleValAnno2); } return UNKNOWNVAL; } if (intRangeAnno != null && intValAnno != null) { // Convert intValAnno to an @IntRange AnnotationMirror AnnotationMirror intRangeAnno2 = convertIntValToIntRange(intValAnno); return leastUpperBound(intRangeAnno, intRangeAnno2); } // In all other cases, the LUB is UnknownVal. return UNKNOWNVAL; } /** * Computes subtyping as per the subtyping in the qualifier hierarchy structure unless both * annotations are Value. In this case, subAnno is a subtype of superAnno iff superAnno * contains at least every element of subAnno. * * @return true if subAnno is a subtype of superAnno, false otherwise */ @Override public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) { if (AnnotationUtils.areSameByClass(superAnno, UnknownVal.class) || AnnotationUtils.areSameByClass(subAnno, BottomVal.class)) { return true; } else if (AnnotationUtils.areSameByClass(subAnno, UnknownVal.class) || AnnotationUtils.areSameByClass(superAnno, BottomVal.class)) { return false; } else if (AnnotationUtils.areSameByClass(subAnno, PolyValue.class)) { return AnnotationUtils.areSameByClass(superAnno, PolyValue.class); } else if (AnnotationUtils.areSameByClass(superAnno, PolyValue.class)) { return AnnotationUtils.areSameByClass(subAnno, PolyValue.class); } else if (AnnotationUtils.areSameIgnoringValues(superAnno, subAnno)) { // Same type, so might be subtype if (AnnotationUtils.areSameByClass(subAnno, IntRange.class) || AnnotationUtils.areSameByClass(subAnno, ArrayLenRange.class)) { // Special case for range-based annotations Range superRange = getRange(superAnno); Range subRange = getRange(subAnno); return superRange.contains(subRange); } else { List<Object> superValues = AnnotationUtils.getElementValueArray( superAnno, "value", Object.class, true); List<Object> subValues = AnnotationUtils.getElementValueArray( subAnno, "value", Object.class, true); return superValues.containsAll(subValues); } } else if (AnnotationUtils.areSameByClass(superAnno, DoubleVal.class) && AnnotationUtils.areSameByClass(subAnno, IntVal.class)) { List<Double> subValues = convertLongListToDoubleList( AnnotationUtils.getElementValueArray( subAnno, "value", Long.class, true)); List<Double> superValues = AnnotationUtils.getElementValueArray( superAnno, "value", Double.class, true); return superValues.containsAll(subValues); } else if ((AnnotationUtils.areSameByClass(superAnno, IntRange.class) && AnnotationUtils.areSameByClass(subAnno, IntVal.class)) || (AnnotationUtils.areSameByClass(superAnno, ArrayLenRange.class) && AnnotationUtils.areSameByClass(subAnno, ArrayLen.class))) { List<Long> subValues = getArrayLenOrIntValue(subAnno); Range superRange = getRange(superAnno); long subMinVal = Collections.min(subValues); long subMaxVal = Collections.max(subValues); return subMinVal >= superRange.from && subMaxVal <= superRange.to; } else if (AnnotationUtils.areSameByClass(superAnno, DoubleVal.class) && AnnotationUtils.areSameByClass(subAnno, IntRange.class)) { Range subRange = getRange(subAnno); if (subRange.isWiderThan(MAX_VALUES)) { return false; } List<Double> superValues = AnnotationUtils.getElementValueArray( superAnno, "value", Double.class, true); List<Double> subValues = ValueCheckerUtils.getValuesFromRange(subRange, Double.class); return superValues.containsAll(subValues); } else if ((AnnotationUtils.areSameByClass(superAnno, IntVal.class) && AnnotationUtils.areSameByClass(subAnno, IntRange.class)) || (AnnotationUtils.areSameByClass(superAnno, ArrayLen.class) && AnnotationUtils.areSameByClass(subAnno, ArrayLenRange.class))) { Range subRange = getRange(subAnno); if (subRange.isWiderThan(MAX_VALUES)) { return false; } List<Long> superValues = getArrayLenOrIntValue(superAnno); List<Long> subValues = ValueCheckerUtils.getValuesFromRange(subRange, Long.class); return superValues.containsAll(subValues); } else { return false; } } } /** * Gets the values stored in either an ArrayLen annotation (ints) or an IntVal/DoubleVal/etc. * annotation (longs), and casts the result to a long. */ private List<Long> getArrayLenOrIntValue(AnnotationMirror anno) { List<Long> result; if (AnnotationUtils.areSameByClass(anno, ArrayLen.class)) { List<Integer> intValues = AnnotationUtils.getElementValueArray(anno, "value", Integer.class, true); result = new ArrayList<Long>(intValues.size()); for (Integer i : intValues) { result.add(i.longValue()); } } else { result = AnnotationUtils.getElementValueArray(anno, "value", Long.class, true); } return result; } @Override protected TreeAnnotator createTreeAnnotator() { // Only use the PropagationTreeAnnotator for typing new arrays. The Value Checker // computes types differently for all other trees normally typed by the // PropagationTreeAnnotator. TreeAnnotator arrayCreation = new TreeAnnotator(this) { PropagationTreeAnnotator propagationTreeAnnotator = new PropagationTreeAnnotator(atypeFactory); @Override public Void visitNewArray(NewArrayTree node, AnnotatedTypeMirror mirror) { return propagationTreeAnnotator.visitNewArray(node, mirror); } }; return new ListTreeAnnotator( new ValueTreeAnnotator(this), new ImplicitsTreeAnnotator(this), arrayCreation); } /** The TreeAnnotator for this AnnotatedTypeFactory. It adds/replaces annotations. */ protected class ValueTreeAnnotator extends TreeAnnotator { public ValueTreeAnnotator(ValueAnnotatedTypeFactory factory) { super(factory); } @Override public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { List<? extends ExpressionTree> dimensions = tree.getDimensions(); List<? extends ExpressionTree> initializers = tree.getInitializers(); // Array construction can provide dimensions or use an initializer. // Dimensions provided if (!dimensions.isEmpty()) { handleDimensions(dimensions, (AnnotatedArrayType) type); } else { // Initializer used handleInitializers(initializers, (AnnotatedArrayType) type); AnnotationMirror newQual; Class<?> clazz = ValueCheckerUtils.getClassFromType(type.getUnderlyingType()); String stringVal = null; if (clazz.equals(byte[].class)) { stringVal = getByteArrayStringVal(initializers); } else if (clazz.equals(char[].class)) { stringVal = getCharArrayStringVal(initializers); } if (stringVal != null) { newQual = createStringAnnotation(Collections.singletonList(stringVal)); type.replaceAnnotation(newQual); } } return null; } /** * Recursive method to handle array initializations. Recursively descends the initializer to * find each dimension's size and create the appropriate annotation for it. * * <p>If the annotation of the dimension is {@code @IntVal}, create an {@code @ArrayLen} * with the same set of possible values. If the annotation is {@code @IntRange}, create an * {@code @ArrayLenRange}. If the annotation is {@code @BottomVal}, create an * {@code @BottomVal} instead. In other cases, no annotations are created. * * @param dimensions a list of ExpressionTrees where each ExpressionTree is a specifier of * the size of that dimension * @param type the AnnotatedTypeMirror of the array */ private void handleDimensions( List<? extends ExpressionTree> dimensions, AnnotatedArrayType type) { if (dimensions.size() > 1) { handleDimensions( dimensions.subList(1, dimensions.size()), (AnnotatedArrayType) type.getComponentType()); } AnnotationMirror dimType = getAnnotatedType(dimensions.get(0)).getAnnotationInHierarchy(UNKNOWNVAL); if (AnnotationUtils.areSameIgnoringValues(dimType, BOTTOMVAL)) { type.replaceAnnotation(BOTTOMVAL); } else { RangeOrListOfValues rolv = null; if (AnnotationUtils.areSameByClass(dimType, IntRange.class)) { rolv = new RangeOrListOfValues(getRange(dimType)); } else if (AnnotationUtils.areSameByClass(dimType, IntVal.class)) { rolv = new RangeOrListOfValues( RangeOrListOfValues.convertLongsToInts(getIntValues(dimType))); } if (rolv != null) { AnnotationMirror newQual = rolv.createAnnotation((ValueAnnotatedTypeFactory) atypeFactory); type.replaceAnnotation(newQual); } } } /** * Adds the ArrayLen/ArrayLenRange annotation from the array initializers to {@code type}. * * <p>If type is a multi-dimensional array, the initializers might also contain arrays, so * this method adds the annotations for those initializers, too. * * @param initializers initializer trees * @param type array type to which annotations are added */ private void handleInitializers( List<? extends ExpressionTree> initializers, AnnotatedArrayType type) { List<Integer> array = new ArrayList<>(); array.add(initializers.size()); type.replaceAnnotation(createArrayLenAnnotation(array)); if (type.getComponentType().getKind() != TypeKind.ARRAY) { return; } // A list of arrayLens. arrayLenOfDimensions.get(i) is the array lengths for the ith // dimension. List<RangeOrListOfValues> arrayLenOfDimensions = new ArrayList<>(); for (ExpressionTree init : initializers) { AnnotatedTypeMirror componentType = getAnnotatedType(init); int dimension = 0; while (componentType.getKind() == TypeKind.ARRAY) { if (dimension == arrayLenOfDimensions.size()) { arrayLenOfDimensions.add(new RangeOrListOfValues()); } RangeOrListOfValues rolv = arrayLenOfDimensions.get(dimension); AnnotationMirror arrayLen = componentType.getAnnotation(ArrayLen.class); if (arrayLen != null) { List<Integer> currentLengths = getArrayLength(arrayLen); rolv.addAll(currentLengths); } else { // Check for an arrayLenRange annotation AnnotationMirror arrayLenRangeAnno = componentType.getAnnotation(ArrayLenRange.class); if (arrayLenRangeAnno != null) { Range range = getRange(arrayLenRangeAnno); rolv.add(range); } } // replace the current dimension's range with this one. arrayLenOfDimensions.remove(dimension); arrayLenOfDimensions.add(dimension, rolv); dimension++; componentType = ((AnnotatedArrayType) componentType).getComponentType(); } } AnnotatedTypeMirror componentType = type.getComponentType(); int i = 0; while (componentType.getKind() == TypeKind.ARRAY && i < arrayLenOfDimensions.size()) { RangeOrListOfValues rolv = arrayLenOfDimensions.get(i); componentType.addAnnotation( rolv.createAnnotation((ValueAnnotatedTypeFactory) atypeFactory)); componentType = ((AnnotatedArrayType) componentType).getComponentType(); i++; } } /** Convert a byte array to a String. Return null if unable to convert. */ private String getByteArrayStringVal(List<? extends ExpressionTree> initializers) { // True iff every element of the array is a literal. boolean allLiterals = true; byte[] bytes = new byte[initializers.size()]; for (int i = 0; i < initializers.size(); i++) { ExpressionTree e = initializers.get(i); if (e.getKind() == Tree.Kind.INT_LITERAL) { bytes[i] = (byte) (((Integer) ((LiteralTree) e).getValue()).intValue()); } else if (e.getKind() == Tree.Kind.CHAR_LITERAL) { bytes[i] = (byte) (((Character) ((LiteralTree) e).getValue()).charValue()); } else { allLiterals = false; } } if (allLiterals) { return new String(bytes); } // If any part of the initializer isn't known, // the stringval isn't known. return null; } /** Convert a char array to a String. Return null if unable to convert. */ private String getCharArrayStringVal(List<? extends ExpressionTree> initializers) { boolean allLiterals = true; StringBuilder stringVal = new StringBuilder(); for (ExpressionTree e : initializers) { if (e.getKind() == Tree.Kind.INT_LITERAL) { char charVal = (char) (((Integer) ((LiteralTree) e).getValue()).intValue()); stringVal.append(charVal); } else if (e.getKind() == Tree.Kind.CHAR_LITERAL) { char charVal = (((Character) ((LiteralTree) e).getValue())); stringVal.append(charVal); } else { allLiterals = false; } } if (allLiterals) { return stringVal.toString(); } // If any part of the initializer isn't known, // the stringval isn't known. return null; } @Override public Void visitTypeCast(TypeCastTree tree, AnnotatedTypeMirror atm) { if (handledByValueChecker(atm)) { AnnotationMirror oldAnno = getAnnotatedType(tree.getExpression()).getAnnotationInHierarchy(UNKNOWNVAL); if (oldAnno != null) { TypeMirror newType = atm.getUnderlyingType(); AnnotationMirror newAnno; Range range; if (AnnotationUtils.areSameByClass(oldAnno, IntRange.class) && (range = getRange(oldAnno)).isWiderThan(MAX_VALUES)) { Class<?> newClass = ValueCheckerUtils.getClassFromType(newType); if (newClass == String.class) { newAnno = UNKNOWNVAL; } else if (newClass == Boolean.class || newClass == boolean.class) { throw new UnsupportedOperationException( "ValueAnnotatedTypeFactory: can't convert int to boolean"); } else { newAnno = createIntRangeAnnotation(NumberUtils.castRange(newType, range)); } } else { List<?> values = ValueCheckerUtils.getValuesCastedToType(oldAnno, newType); newAnno = createResultingAnnotation(atm.getUnderlyingType(), values); } atm.replaceAnnotation(newAnno); } } else if (atm.getKind() == TypeKind.ARRAY) { if (tree.getExpression().getKind() == Kind.NULL_LITERAL) { atm.replaceAnnotation(BOTTOMVAL); } } return null; } /** * Get the "value" field of the given annotation, casted to the given type. Empty list means * no value is possible (dead code). Null means no information is known -- any value is * possible. */ private List<?> getValues(AnnotatedTypeMirror type, TypeMirror castTo) { AnnotationMirror anno = type.getAnnotationInHierarchy(UNKNOWNVAL); if (anno == null) { // If type is an AnnotatedTypeVariable (or other type without a primary annotation) // then anno will be null. It would be safe to use the annotation on the upper bound; // however, unless the upper bound was explicitly annotated, it will be unknown. // AnnotatedTypes.findEffectiveAnnotationInHierarchy(, toSearch, top) return null; } return ValueCheckerUtils.getValuesCastedToType(anno, castTo); } @Override public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { if (!handledByValueChecker(type)) { return null; } Object value = tree.getValue(); switch (tree.getKind()) { case BOOLEAN_LITERAL: AnnotationMirror boolAnno = createBooleanAnnotation(Collections.singletonList((Boolean) value)); type.replaceAnnotation(boolAnno); return null; case CHAR_LITERAL: AnnotationMirror charAnno = createCharAnnotation(Collections.singletonList((Character) value)); type.replaceAnnotation(charAnno); return null; case DOUBLE_LITERAL: case FLOAT_LITERAL: case INT_LITERAL: case LONG_LITERAL: AnnotationMirror numberAnno = createNumberAnnotationMirror(Collections.singletonList((Number) value)); type.replaceAnnotation(numberAnno); return null; case STRING_LITERAL: AnnotationMirror stringAnno = createStringAnnotation(Collections.singletonList((String) value)); type.replaceAnnotation(stringAnno); return null; default: return null; } } /** * Given a MemberSelectTree representing a method call, return true if the method's * declaration is annotated with {@code @StaticallyExecutable}. */ private boolean methodIsStaticallyExecutable(Element method) { return getDeclAnnotation(method, StaticallyExecutable.class) != null; } @Override public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { if (handledByValueChecker(type) && methodIsStaticallyExecutable(TreeUtils.elementFromUse(tree))) { // Get argument values List<? extends ExpressionTree> arguments = tree.getArguments(); ArrayList<List<?>> argValues; if (arguments.size() > 0) { argValues = new ArrayList<List<?>>(); for (ExpressionTree argument : arguments) { AnnotatedTypeMirror argType = getAnnotatedType(argument); List<?> values = getValues(argType, argType.getUnderlyingType()); if (values == null || values.isEmpty()) { // Values aren't known, so don't try to evaluate the method. return null; } argValues.add(values); } } else { argValues = null; } // Get receiver values AnnotatedTypeMirror receiver = getReceiverType(tree); List<?> receiverValues; if (receiver != null && !ElementUtils.isStatic(TreeUtils.elementFromUse(tree))) { receiverValues = getValues(receiver, receiver.getUnderlyingType()); if (receiverValues == null || receiverValues.isEmpty()) { // Values aren't known, so don't try to evaluate the method. return null; } } else { receiverValues = null; } // Evaluate method List<?> returnValues = evaluator.evaluateMethodCall(argValues, receiverValues, tree); if (returnValues == null) { return null; } AnnotationMirror returnType = createResultingAnnotation(type.getUnderlyingType(), returnValues); type.replaceAnnotation(returnType); } return null; } @Override public Void visitNewClass(NewClassTree tree, AnnotatedTypeMirror type) { boolean wrapperClass = TypesUtils.isBoxedPrimitive(type.getUnderlyingType()) || TypesUtils.isDeclaredOfName( type.getUnderlyingType(), "java.lang.String"); if (wrapperClass || (handledByValueChecker(type) && methodIsStaticallyExecutable(TreeUtils.elementFromUse(tree)))) { // get argument values List<? extends ExpressionTree> arguments = tree.getArguments(); ArrayList<List<?>> argValues; if (arguments.size() > 0) { argValues = new ArrayList<List<?>>(); for (ExpressionTree argument : arguments) { AnnotatedTypeMirror argType = getAnnotatedType(argument); List<?> values = getValues(argType, argType.getUnderlyingType()); if (values == null || values.isEmpty()) { // Values aren't known, so don't try to evaluate the method. return null; } argValues.add(values); } } else { argValues = null; } // Evaluate method List<?> returnValues = evaluator.evaluteConstructorCall(argValues, tree, type.getUnderlyingType()); if (returnValues == null) { return null; } AnnotationMirror returnType = createResultingAnnotation(type.getUnderlyingType(), returnValues); type.replaceAnnotation(returnType); } return null; } @Override public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) { if (TreeUtils.isFieldAccess(tree) && handledByValueChecker(type)) { VariableElement elem = (VariableElement) InternalUtils.symbol(tree); Object value = elem.getConstantValue(); if (value != null) { // The field is a compile time constant. type.replaceAnnotation( createResultingAnnotation(type.getUnderlyingType(), value)); return null; } if (ElementUtils.isStatic(elem) && ElementUtils.isFinal(elem)) { // The field is static and final. Element e = InternalUtils.symbol(tree.getExpression()); if (e != null) { String classname = ElementUtils.getQualifiedClassName(e).toString(); String fieldName = tree.getIdentifier().toString(); value = evaluator.evaluateStaticFieldAccess(classname, fieldName, tree); if (value != null) { type.replaceAnnotation( createResultingAnnotation(type.getUnderlyingType(), value)); } return null; } } if (TreeUtils.isArrayLengthAccess(tree)) { // The field access is to the length field, as in "someArrayExpression.length" AnnotatedTypeMirror receiverType = getAnnotatedType(tree.getExpression()); if (receiverType.getKind() == TypeKind.ARRAY) { AnnotationMirror arrayAnno = receiverType.getAnnotation(ArrayLen.class); if (arrayAnno != null) { // array.length, where array : @ArrayLen(x) List<Integer> lengths = ValueAnnotatedTypeFactory.getArrayLength(arrayAnno); type.replaceAnnotation( createNumberAnnotationMirror(new ArrayList<Number>(lengths))); return null; } // Check for an ArrayLenRange annotation. arrayAnno = receiverType.getAnnotation(ArrayLenRange.class); if (arrayAnno != null) { // array.length, where array : @ArrayLenRange(x) Range range = getRange(arrayAnno); type.replaceAnnotation(createIntRangeAnnotation(range)); return null; } else { type.replaceAnnotation(createIntRangeAnnotation(0, Integer.MAX_VALUE)); } } } } return null; } /** Returns true iff the given type is in the domain of the Constant Value Checker. */ private boolean handledByValueChecker(AnnotatedTypeMirror type) { return coveredClassStrings.contains(type.getUnderlyingType().toString()); } } /** * Returns a constant value annotation with the {@code value}. The class of the annotation * reflects the {@code resultType} given. * * @param resultType used to selecte which kind of value annotation is returned * @param value value to use * @return a constant value annotation with the {@code value} */ AnnotationMirror createResultingAnnotation(TypeMirror resultType, Object value) { return createResultingAnnotation(resultType, Collections.singletonList(value)); } /** * Returns a constant value annotation with the {@code values}. The class of the annotation * reflects the {@code resultType} given. * * @param resultType used to selected which kind of value annotation is returned * @param values must be a homogeneous list: every element of it has the same class * @return a constant value annotation with the {@code values} */ AnnotationMirror createResultingAnnotation(TypeMirror resultType, List<?> values) { if (values == null) { return UNKNOWNVAL; } // For some reason null is included in the list of values, // so remove it so that it does not cause a NPE elsewhere. values.remove(null); if (values.size() == 0) { return BOTTOMVAL; } if (TypesUtils.isString(resultType)) { List<String> stringVals = new ArrayList<>(values.size()); for (Object o : values) { stringVals.add((String) o); } return createStringAnnotation(stringVals); } else if (ValueCheckerUtils.getClassFromType(resultType) == byte[].class) { List<String> stringVals = new ArrayList<>(values.size()); for (Object o : values) { if (o instanceof byte[]) { stringVals.add(new String((byte[]) o)); } else { stringVals.add(o.toString()); } } return createStringAnnotation(stringVals); } TypeKind primitiveKind; if (TypesUtils.isPrimitive(resultType)) { primitiveKind = resultType.getKind(); } else if (TypesUtils.isBoxedPrimitive(resultType)) { primitiveKind = types.unboxedType(resultType).getKind(); } else { return UNKNOWNVAL; } switch (primitiveKind) { case BOOLEAN: List<Boolean> boolVals = new ArrayList<>(values.size()); for (Object o : values) { boolVals.add((Boolean) o); } return createBooleanAnnotation(boolVals); case DOUBLE: case FLOAT: case INT: case LONG: case SHORT: case BYTE: List<Number> numberVals = new ArrayList<>(values.size()); List<Character> characterVals = new ArrayList<>(values.size()); for (Object o : values) { if (o instanceof Character) { characterVals.add((Character) o); } else { numberVals.add((Number) o); } } if (numberVals.isEmpty()) { return createCharAnnotation(characterVals); } return createNumberAnnotationMirror(new ArrayList<>(numberVals)); case CHAR: List<Character> charVals = new ArrayList<>(values.size()); for (Object o : values) { if (o instanceof Number) { charVals.add((char) ((Number) o).intValue()); } else { charVals.add((char) o); } } return createCharAnnotation(charVals); default: throw new UnsupportedOperationException("Unexpected kind:" + resultType); } } /** * Returns a {@link IntVal} or {@link IntRange} annotation using the values. If {@code values} * is null, then UnknownVal is returned; if {@code values} is empty, then bottom is returned. If * the number of {@code values} is greater than MAX_VALUES, return an {@link IntRange}. In other * cases, the values are sorted and duplicates are removed before an {@link IntVal} is created. * * @param values list of longs; duplicates are allowed and the values may be in any order * @return an annotation depends on the values */ public AnnotationMirror createIntValAnnotation(List<Long> values) { if (values == null) { return UNKNOWNVAL; } if (values.isEmpty()) { return BOTTOMVAL; } values = ValueCheckerUtils.removeDuplicates(values); if (values.size() > MAX_VALUES) { long valMin = Collections.min(values); long valMax = Collections.max(values); return createIntRangeAnnotation(valMin, valMax); } else { AnnotationBuilder builder = new AnnotationBuilder(processingEnv, IntVal.class); builder.setValue("value", values); return builder.build(); } } /** * Convert an {@code @IntRange} annotation to an {@code @IntVal} annotation, or to UNKNOWNVAL if * the input is too wide to be represented as an {@code @IntVal}. */ public AnnotationMirror convertIntRangeToIntVal(AnnotationMirror intRangeAnno) { Range range = getRange(intRangeAnno); List<Long> values = ValueCheckerUtils.getValuesFromRange(range, Long.class); return createIntValAnnotation(values); } /** * Returns a {@link DoubleVal} annotation using the values. If {@code values} is null, then * UnknownVal is returned; if {@code values} is empty, then bottom is returned. The values are * sorted and duplicates are removed before the annotation is created. * * @param values list of doubles; duplicates are allowed and the values may be in any order * @return a {@link DoubleVal} annotation using the values */ public AnnotationMirror createDoubleValAnnotation(List<Double> values) { if (values == null) { return UNKNOWNVAL; } if (values.isEmpty()) { return BOTTOMVAL; } values = ValueCheckerUtils.removeDuplicates(values); if (values.size() > MAX_VALUES) { return UNKNOWNVAL; } else { AnnotationBuilder builder = new AnnotationBuilder(processingEnv, DoubleVal.class); builder.setValue("value", values); return builder.build(); } } /** Convert an {@code @IntVal} annotation to a {@code @DoubleVal} annotation. */ private AnnotationMirror convertIntValToDoubleVal(AnnotationMirror intValAnno) { List<Long> intValues = getIntValues(intValAnno); return createDoubleValAnnotation(convertLongListToDoubleList(intValues)); } /** Convert a {@code List<Long>} to a {@code List<Double>}. */ private List<Double> convertLongListToDoubleList(List<Long> intValues) { List<Double> doubleValues = new ArrayList<Double>(intValues.size()); for (Long intValue : intValues) { doubleValues.add(intValue.doubleValue()); } return doubleValues; } /** * Returns a {@link StringVal} annotation using the values. If {@code values} is null, then * UnknownVal is returned; if {@code values} is empty, then bottom is returned. The values are * sorted and duplicates are removed before the annotation is created. * * @param values list of strings; duplicates are allowed and the values may be in any order * @return a {@link StringVal} annotation using the values */ public AnnotationMirror createStringAnnotation(List<String> values) { if (values == null) { return UNKNOWNVAL; } if (values.isEmpty()) { return BOTTOMVAL; } values = ValueCheckerUtils.removeDuplicates(values); if (values.size() > MAX_VALUES) { return UNKNOWNVAL; } else { AnnotationBuilder builder = new AnnotationBuilder(processingEnv, StringVal.class); builder.setValue("value", values); return builder.build(); } } /** * Returns a {@link ArrayLen} annotation using the values. If {@code values} is null, then * UnknownVal is returned; if {@code values} is empty, then bottom is returned. The values are * sorted and duplicates are removed before the annotation is created. If values is larger than * the max number of values allowed (10 by default), then an {@link ArrayLenRange} annotation is * returned. * * @param values list of integers; duplicates are allowed and the values may be in any order * @return a {@link ArrayLen} annotation using the values */ public AnnotationMirror createArrayLenAnnotation(List<Integer> values) { if (values == null) { return UNKNOWNVAL; } if (values.isEmpty()) { return BOTTOMVAL; } values = ValueCheckerUtils.removeDuplicates(values); if (values.isEmpty() || Collections.min(values) < 0) { return BOTTOMVAL; } else if (values.size() > MAX_VALUES) { return createArrayLenRangeAnnotation(Collections.min(values), Collections.max(values)); } else { AnnotationBuilder builder = new AnnotationBuilder(processingEnv, ArrayLen.class); builder.setValue("value", values); return builder.build(); } } /** * Returns a {@link BoolVal} annotation using the values. If {@code values} is null, then * UnknownVal is returned; if {@code values} is empty, then bottom is returned. The values are * sorted and duplicates are removed before the annotation is created. * * @param values list of booleans; duplicates are allowed and the values may be in any order * @return a {@link BoolVal} annotation using the values */ public AnnotationMirror createBooleanAnnotation(List<Boolean> values) { if (values == null) { return UNKNOWNVAL; } if (values.isEmpty()) { return BOTTOMVAL; } values = ValueCheckerUtils.removeDuplicates(values); if (values.size() > MAX_VALUES) { return UNKNOWNVAL; } else { AnnotationBuilder builder = new AnnotationBuilder(processingEnv, BoolVal.class); builder.setValue("value", values); return builder.build(); } } /** * Returns a {@link IntVal} annotation using the values. If {@code values} is null, then * UnknownVal is returned; if {@code values} is empty, then bottom is returned. The values are * sorted and duplicates are removed before the annotation is created. * * @param values list of characters; duplicates are allowed and the values may be in any order * @return a {@link IntVal} annotation using the values */ public AnnotationMirror createCharAnnotation(List<Character> values) { if (values == null) { return UNKNOWNVAL; } if (values.isEmpty()) { return BOTTOMVAL; } values = ValueCheckerUtils.removeDuplicates(values); if (values.size() > MAX_VALUES) { return UNKNOWNVAL; } else { List<Long> longValues = new ArrayList<>(); for (char value : values) { longValues.add((long) value); } return createIntValAnnotation(longValues); } } /** @param values must be a homogeneous list: every element of it has the same class. */ public AnnotationMirror createNumberAnnotationMirror(List<Number> values) { if (values == null) { return UNKNOWNVAL; } else if (values.isEmpty()) { return BOTTOMVAL; } Number first = values.get(0); if (first instanceof Integer || first instanceof Short || first instanceof Long || first instanceof Byte) { List<Long> intValues = new ArrayList<>(); for (Number number : values) { intValues.add(number.longValue()); } return createIntValAnnotation(intValues); } else if (first instanceof Double || first instanceof Float) { List<Double> intValues = new ArrayList<>(); for (Number number : values) { intValues.add(number.doubleValue()); } return createDoubleValAnnotation(intValues); } throw new UnsupportedOperationException( "ValueAnnotatedTypeFactory: unexpected class: " + first.getClass()); } /** * Create an {@code @IntRange} annotation from the two (inclusive) bounds. Does not return * BOTTOMVAL or UNKNOWNVAL. */ private AnnotationMirror createIntRangeAnnotation(long from, long to) { assert from <= to; AnnotationBuilder builder = new AnnotationBuilder(processingEnv, IntRange.class); builder.setValue("from", from); builder.setValue("to", to); return builder.build(); } /** * Create an {@code @IntRange} or {@code @IntVal} annotation from the range. May return * BOTTOMVAL or UNKNOWNVAL. */ public AnnotationMirror createIntRangeAnnotation(Range range) { if (range.isNothing()) { return BOTTOMVAL; } else if (range.isEverything()) { return UNKNOWNVAL; } else if (range.isWiderThan(MAX_VALUES)) { return createIntRangeAnnotation(range.from, range.to); } else { List<Long> newValues = ValueCheckerUtils.getValuesFromRange(range, Long.class); return createIntValAnnotation(newValues); } } /** * Create an {@code @ArrayLenRange} annotation from the two (inclusive) bounds. Does not return * BOTTOMVAL or UNKNOWNVAL. */ public AnnotationMirror createArrayLenRangeAnnotation(int from, int to) { assert from <= to; AnnotationBuilder builder = new AnnotationBuilder(processingEnv, ArrayLenRange.class); builder.setValue("from", from); builder.setValue("to", to); return builder.build(); } /** * Create an {@code @ArrayLenRange} annotation from the range. May return BOTTOMVAL or * UNKNOWNVAL. */ public AnnotationMirror createArrayLenRangeAnnotation(Range range) { if (range.isNothing()) { return BOTTOMVAL; } else if (range.isEverything() || !range.isWithin(Integer.MIN_VALUE, Integer.MAX_VALUE)) { return UNKNOWNVAL; } else { return createArrayLenRangeAnnotation( Long.valueOf(range.from).intValue(), Long.valueOf(range.to).intValue()); } } /** Converts an {@code @ArrayLen} annotation to an {@code @ArrayLenRange} annotation. */ public AnnotationMirror convertArrayLenToArrayLenRange(AnnotationMirror arrayLenAnno) { List<Integer> values = getArrayLength(arrayLenAnno); return createArrayLenRangeAnnotation(Collections.min(values), Collections.max(values)); } /** Converts an {@code @IntVal} annotation to an {@code @IntRange} annotation. */ public AnnotationMirror convertIntValToIntRange(AnnotationMirror intValAnno) { List<Long> intValues = getIntValues(intValAnno); return createIntRangeAnnotation(Collections.min(intValues), Collections.max(intValues)); } /** Returns a {@code Range} bounded by the values specified in the given annotation. */ public static Range getRange(AnnotationMirror rangeAnno) { if (rangeAnno == null) { return null; } // Assume rangeAnno is well-formed, i.e., 'from' is less than or equal to 'to'. if (AnnotationUtils.areSameByClass(rangeAnno, IntRange.class)) { return new Range( AnnotationUtils.getElementValue(rangeAnno, "from", Long.class, true), AnnotationUtils.getElementValue(rangeAnno, "to", Long.class, true)); } else { return new Range( AnnotationUtils.getElementValue(rangeAnno, "from", Integer.class, true), AnnotationUtils.getElementValue(rangeAnno, "to", Integer.class, true)); } } /** * Returns the set of possible values as a sorted listed with no duplicate values. Returns the * empty list if no values are possible (for dead code). Returns null if any value is possible * -- that is, if no estimate can be made -- and this includes when there is no constant-value * annotation so the argument is null. * * <p>The method returns a list of {@code Long} but is named {@code getIntValues} because it * supports the {@code @IntVal} annotation. * * @param intAnno an {@code @IntVal} annotation, or null */ public static List<Long> getIntValues(AnnotationMirror intAnno) { if (intAnno == null) { return null; } List<Long> list = AnnotationUtils.getElementValueArray(intAnno, "value", Long.class, true); ValueCheckerUtils.removeDuplicates(list); return list; } /** * Returns the set of possible values as a sorted listed with no duplicate values. Returns the * empty list if no values are possible (for dead code). Returns null if any value is possible * -- that is, if no estimate can be made -- and this includes when there is no constant-value * annotation so the argument is null. * * @param doubleAnno a {@code @DoubleVal} annotation, or null */ public static List<Double> getDoubleValues(AnnotationMirror doubleAnno) { if (doubleAnno == null) { return null; } List<Double> list = AnnotationUtils.getElementValueArray(doubleAnno, "value", Double.class, true); ValueCheckerUtils.removeDuplicates(list); return list; } /** * Returns the set of possible array lengths as a sorted listed with no duplicate values. * Returns the empty list if no values are possible (for dead code). Returns null if any value * is possible -- that is, if no estimate can be made -- and this includes when there is no * constant-value annotation so the argument is null. * * @param arrayAnno an {@code @ArrayLen} annotation, or null */ public static List<Integer> getArrayLength(AnnotationMirror arrayAnno) { if (arrayAnno == null) { return null; } List<Integer> list = AnnotationUtils.getElementValueArray(arrayAnno, "value", Integer.class, true); ValueCheckerUtils.removeDuplicates(list); return list; } /** * Returns the set of possible values as a sorted listed with no duplicate values. Returns the * empty list if no values are possible (for dead code). Returns null if any value is possible * -- that is, if no estimate can be made -- and this includes when there is no constant-value * annotation so the argument is null. * * @param intAnno an {@code @IntVal} annotation, or null */ public static List<Character> getCharValues(AnnotationMirror intAnno) { if (intAnno == null) { return new ArrayList<>(); } List<Long> intValues = AnnotationUtils.getElementValueArray(intAnno, "value", Long.class, true); TreeSet<Character> charValues = new TreeSet<>(); for (Long i : intValues) { charValues.add((char) i.intValue()); } return new ArrayList<>(charValues); } /** * Returns the set of possible values as a sorted listed with no duplicate values. Returns the * empty list if no values are possible (for dead code). Returns null if any value is possible * -- that is, if no estimate can be made -- and this includes when there is no constant-value * annotation so the argument is null. * * @param boolAnno a {@code @BoolVal} annotation, or null */ public static List<Boolean> getBooleanValues(AnnotationMirror boolAnno) { if (boolAnno == null) { return new ArrayList<>(); } List<Boolean> boolValues = AnnotationUtils.getElementValueArray(boolAnno, "value", Boolean.class, true); Set<Boolean> boolSet = new TreeSet<>(boolValues); if (boolSet.size() > 1) { // boolSet={true,false}; return null; } return new ArrayList<>(boolSet); } public Integer getMinLenValue(AnnotatedTypeMirror atm) { return getMinLenValue(atm.getAnnotationInHierarchy(UNKNOWNVAL)); } /** * Used to find the minimum length of an array, which is useful for array bounds checking. * Returns null if there is no minimum length known, or if the passed annotation is null. * * <p>Note that this routine handles actual {@link MinLen} annotations, because it is called by * {@link ValueAnnotatedTypeFactory#aliasedAnnotation(AnnotationMirror)}, which transforms * {@link MinLen} annotations into {@link ArrayLenRange} annotations. */ public Integer getMinLenValue(AnnotationMirror annotation) { if (annotation == null) { return null; } if (AnnotationUtils.areSameByClass(annotation, MinLen.class)) { return AnnotationUtils.getElementValue(annotation, "value", Integer.class, true); } else if (AnnotationUtils.areSameByClass(annotation, ArrayLenRange.class)) { return Long.valueOf(getRange(annotation).from).intValue(); } else if (AnnotationUtils.areSameByClass(annotation, ArrayLen.class)) { return Collections.min(getArrayLength(annotation)); } else { return null; } } /** * Returns the smallest possible value that an integral annotation might take on. The passed * {@code AnnotatedTypeMirror} should contain either an {@code @IntRange} annotation or an * {@code @IntVal} annotation. Returns null if it does not. */ public Long getMinimumIntegralValue(AnnotatedTypeMirror atm) { AnnotationMirror anm = atm.getAnnotationInHierarchy(UNKNOWNVAL); if (AnnotationUtils.areSameByClass(anm, IntVal.class)) { List<Long> possibleValues = getIntValues(anm); return Collections.min(possibleValues); } else if (AnnotationUtils.areSameByClass(anm, IntRange.class)) { Range range = getRange(anm); return range.from; } return null; } /** * Returns the minimum length of an array expression or 0 if the min length is unknown. * * @param arrayExpression flow expression * @param tree expression tree or variable declaration * @param currentPath path to local scope * @return min length of arrayExpression or 0 */ public int getMinLenFromString(String arrayExpression, Tree tree, TreePath currentPath) { AnnotationMirror lengthAnno = null; try { lengthAnno = getAnnotationFromJavaExpressionString( arrayExpression, tree, currentPath, ArrayLenRange.class); if (lengthAnno == null) { lengthAnno = getAnnotationFromJavaExpressionString( arrayExpression, tree, currentPath, ArrayLen.class); } } catch (FlowExpressionParseException e) { // ignore parse errors } if (lengthAnno == null) { // Could not find a more precise type, so return 0; return 0; } Integer minLenValue = getMinLenValue(lengthAnno); return minLenValue == null ? 0 : minLenValue; } }