| package org.checkerframework.common.value; |
| |
| import com.sun.source.tree.ExpressionTree; |
| import com.sun.source.tree.NewArrayTree; |
| import com.sun.source.tree.Tree; |
| 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 java.util.regex.Pattern; |
| 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.type.TypeKind; |
| import javax.lang.model.type.TypeMirror; |
| import org.checkerframework.checker.nullness.qual.Nullable; |
| import org.checkerframework.checker.regex.qual.Regex; |
| 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.EnumVal; |
| import org.checkerframework.common.value.qual.IntRange; |
| import org.checkerframework.common.value.qual.IntRangeFromGTENegativeOne; |
| import org.checkerframework.common.value.qual.IntRangeFromNonNegative; |
| import org.checkerframework.common.value.qual.IntRangeFromPositive; |
| import org.checkerframework.common.value.qual.IntVal; |
| import org.checkerframework.common.value.qual.MatchesRegex; |
| 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.StringVal; |
| import org.checkerframework.common.value.qual.UnknownVal; |
| import org.checkerframework.common.value.util.Range; |
| import org.checkerframework.dataflow.expression.ArrayAccess; |
| import org.checkerframework.dataflow.expression.ArrayCreation; |
| import org.checkerframework.dataflow.expression.JavaExpression; |
| import org.checkerframework.dataflow.expression.ValueLiteral; |
| 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.type.AnnotatedTypeMirror; |
| import org.checkerframework.framework.type.DefaultTypeHierarchy; |
| import org.checkerframework.framework.type.QualifierHierarchy; |
| import org.checkerframework.framework.type.StructuralEqualityComparer; |
| import org.checkerframework.framework.type.TypeHierarchy; |
| import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator; |
| import org.checkerframework.framework.type.treeannotator.LiteralTreeAnnotator; |
| 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.FieldInvariants; |
| import org.checkerframework.framework.util.JavaExpressionParseUtil.JavaExpressionParseException; |
| import org.checkerframework.javacutil.AnnotationBuilder; |
| import org.checkerframework.javacutil.AnnotationUtils; |
| import org.checkerframework.javacutil.BugInCF; |
| import org.checkerframework.javacutil.ElementUtils; |
| import org.checkerframework.javacutil.TreeUtils; |
| import org.checkerframework.javacutil.TypeKindUtils; |
| import org.checkerframework.javacutil.TypesUtils; |
| import org.plumelib.util.CollectionsPlume; |
| |
| /** AnnotatedTypeFactory for the Value type system. */ |
| public class ValueAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { |
| /** Fully-qualified class name of {@link UnknownVal}. */ |
| public static final String UNKNOWN_NAME = "org.checkerframework.common.value.qual.UnknownVal"; |
| /** Fully-qualified class name of {@link BottomVal}. */ |
| public static final String BOTTOMVAL_NAME = "org.checkerframework.common.value.qual.BottomVal"; |
| /** Fully-qualified class name of {@link PolyValue}. */ |
| public static final String POLY_NAME = "org.checkerframework.common.value.qual.PolyValue"; |
| /** Fully-qualified class name of {@link ArrayLen}. */ |
| public static final String ARRAYLEN_NAME = "org.checkerframework.common.value.qual.ArrayLen"; |
| /** Fully-qualified class name of {@link BoolVal}. */ |
| public static final String BOOLVAL_NAME = "org.checkerframework.common.value.qual.BoolVal"; |
| /** Fully-qualified class name of {@link DoubleVal}. */ |
| public static final String DOUBLEVAL_NAME = "org.checkerframework.common.value.qual.DoubleVal"; |
| /** Fully-qualified class name of {@link IntVal}. */ |
| public static final String INTVAL_NAME = "org.checkerframework.common.value.qual.IntVal"; |
| /** Fully-qualified class name of {@link StringVal}. */ |
| public static final String STRINGVAL_NAME = "org.checkerframework.common.value.qual.StringVal"; |
| /** Fully-qualified class name of {@link ArrayLenRange}. */ |
| public static final String ARRAYLENRANGE_NAME = |
| "org.checkerframework.common.value.qual.ArrayLenRange"; |
| /** Fully-qualified class name of {@link IntRange}. */ |
| public static final String INTRANGE_NAME = "org.checkerframework.common.value.qual.IntRange"; |
| |
| /** Fully-qualified class name of {@link IntRangeFromGTENegativeOne}. */ |
| public static final String INTRANGE_FROMGTENEGONE_NAME = |
| "org.checkerframework.common.value.qual.IntRangeFromGTENegativeOne"; |
| /** Fully-qualified class name of {@link IntRangeFromNonNegative}. */ |
| public static final String INTRANGE_FROMNONNEG_NAME = |
| "org.checkerframework.common.value.qual.IntRangeFromNonNegative"; |
| /** Fully-qualified class name of {@link IntRangeFromPositive}. */ |
| public static final String INTRANGE_FROMPOS_NAME = |
| "org.checkerframework.common.value.qual.IntRangeFromPositive"; |
| /** Fully-qualified class name of {@link MinLen}. */ |
| public static final String MINLEN_NAME = "org.checkerframework.common.value.qual.MinLen"; |
| /** Fully-qualified class name of {@link MatchesRegex}. */ |
| public static final String MATCHES_REGEX_NAME = |
| "org.checkerframework.common.value.qual.MatchesRegex"; |
| |
| /** The maximum number of values allowed in an annotation's array. */ |
| protected static final int MAX_VALUES = 10; |
| |
| /** The top type for this hierarchy. */ |
| protected final AnnotationMirror UNKNOWNVAL = |
| AnnotationBuilder.fromClass(elements, UnknownVal.class); |
| |
| /** The bottom type for this hierarchy. */ |
| protected final AnnotationMirror BOTTOMVAL = |
| AnnotationBuilder.fromClass(elements, BottomVal.class); |
| |
| /** The canonical @{@link PolyValue} annotation. */ |
| public final AnnotationMirror POLY = AnnotationBuilder.fromClass(elements, PolyValue.class); |
| |
| /** The canonical @{@link BoolVal}(true) annotation. */ |
| public final AnnotationMirror BOOLEAN_TRUE = |
| createBooleanAnnotation(Collections.singletonList(true)); |
| |
| /** The canonical @{@link BoolVal}(false) annotation. */ |
| public final AnnotationMirror BOOLEAN_FALSE = |
| createBooleanAnnotation(Collections.singletonList(false)); |
| |
| /** The value() element/field of an @ArrayLen annotation. */ |
| protected final ExecutableElement arrayLenValueElement = |
| TreeUtils.getMethod(ArrayLen.class, "value", 0, processingEnv); |
| /** The from() element/field of an @ArrayLenRange annotation. */ |
| protected final ExecutableElement arrayLenRangeFromElement = |
| TreeUtils.getMethod(ArrayLenRange.class, "from", 0, processingEnv); |
| /** The to() element/field of an @ArrayLenRange annotation. */ |
| protected final ExecutableElement arrayLenRangeToElement = |
| TreeUtils.getMethod(ArrayLenRange.class, "to", 0, processingEnv); |
| /** The value() element/field of a @BoolVal annotation. */ |
| protected final ExecutableElement boolValValueElement = |
| TreeUtils.getMethod(BoolVal.class, "value", 0, processingEnv); |
| /** The value() element/field of a @DoubleVal annotation. */ |
| protected final ExecutableElement doubleValValueElement = |
| TreeUtils.getMethod(DoubleVal.class, "value", 0, processingEnv); |
| /** The from() element/field of an @IntRange annotation. */ |
| protected final ExecutableElement intRangeFromElement = |
| TreeUtils.getMethod(IntRange.class, "from", 0, processingEnv); |
| /** The to() element/field of an @IntRange annotation. */ |
| protected final ExecutableElement intRangeToElement = |
| TreeUtils.getMethod(IntRange.class, "to", 0, processingEnv); |
| /** The value() element/field of a @IntVal annotation. */ |
| protected final ExecutableElement intValValueElement = |
| TreeUtils.getMethod(IntVal.class, "value", 0, processingEnv); |
| /** The value() element/field of a @MatchesRegex annotation. */ |
| public final ExecutableElement matchesRegexValueElement = |
| TreeUtils.getMethod(MatchesRegex.class, "value", 0, processingEnv); |
| /** The value() element/field of a @MinLen annotation. */ |
| protected final ExecutableElement minLenValueElement = |
| TreeUtils.getMethod(MinLen.class, "value", 0, processingEnv); |
| /** The field() element/field of a @MinLenFieldInvariant annotation. */ |
| protected final ExecutableElement minLenFieldInvariantFieldElement = |
| TreeUtils.getMethod(MinLenFieldInvariant.class, "field", 0, processingEnv); |
| /** The minLen() element/field of a @MinLenFieldInvariant annotation. */ |
| protected final ExecutableElement minLenFieldInvariantMinLenElement = |
| TreeUtils.getMethod(MinLenFieldInvariant.class, "minLen", 0, processingEnv); |
| /** The value() element/field of a @StringVal annotation. */ |
| public final ExecutableElement stringValValueElement = |
| TreeUtils.getMethod(StringVal.class, "value", 0, processingEnv); |
| |
| /** Should this type factory report warnings? */ |
| private final boolean reportEvalWarnings; |
| |
| /** Helper class that evaluates statically executable methods, constructors, and fields. */ |
| // TODO: only used in ValueTreeAnnotator. Should it move there? |
| protected final ReflectiveEvaluator evaluator; |
| |
| /** Helper class that holds references to special methods. */ |
| private final ValueMethodIdentifier methods; |
| |
| @SuppressWarnings("StaticAssignmentInConstructor") // static Range.ignoreOverflow is gross |
| public ValueAnnotatedTypeFactory(BaseTypeChecker checker) { |
| super(checker); |
| |
| reportEvalWarnings = checker.hasOption(ValueChecker.REPORT_EVAL_WARNS); |
| Range.ignoreOverflow = checker.hasOption(ValueChecker.IGNORE_RANGE_OVERFLOW); |
| evaluator = new ReflectiveEvaluator(checker, this, reportEvalWarnings); |
| |
| addAliasedTypeAnnotation("android.support.annotation.IntRange", IntRange.class, true); |
| |
| // The actual ArrayLenRange is created by |
| // {@link ValueAnnotatedTypeFactory#canonicalAnnotation(AnnotationMirror)}; |
| // this line just registers the alias. The BottomVal is never used. |
| addAliasedTypeAnnotation(MinLen.class, BOTTOMVAL); |
| |
| // @Positive is aliased here because @Positive provides useful |
| // information about @MinLen annotations. |
| // @NonNegative and @GTENegativeOne are aliased similarly so |
| // that it's possible to overwrite a function annotated to return |
| // @NonNegative with, for instance, a function that returns an @IntVal(0). |
| addAliasedTypeAnnotation( |
| "org.checkerframework.checker.index.qual.Positive", createIntRangeFromPositive()); |
| addAliasedTypeAnnotation( |
| "org.checkerframework.checker.index.qual.NonNegative", createIntRangeFromNonNegative()); |
| addAliasedTypeAnnotation( |
| "org.checkerframework.checker.index.qual.GTENegativeOne", |
| createIntRangeFromGTENegativeOne()); |
| // Must also alias any alias of three annotations above: |
| addAliasedTypeAnnotation( |
| "org.checkerframework.checker.index.qual.LengthOf", createIntRangeFromNonNegative()); |
| addAliasedTypeAnnotation( |
| "org.checkerframework.checker.index.qual.IndexFor", createIntRangeFromNonNegative()); |
| addAliasedTypeAnnotation( |
| "org.checkerframework.checker.index.qual.IndexOrHigh", createIntRangeFromNonNegative()); |
| addAliasedTypeAnnotation( |
| "org.checkerframework.checker.index.qual.IndexOrLow", createIntRangeFromGTENegativeOne()); |
| addAliasedTypeAnnotation( |
| "org.checkerframework.checker.index.qual.SubstringIndexFor", |
| createIntRangeFromGTENegativeOne()); |
| |
| // PolyLength is syntactic sugar for both @PolySameLen and @PolyValue |
| addAliasedTypeAnnotation("org.checkerframework.checker.index.qual.PolyLength", POLY); |
| |
| // EnumVal is treated as StringVal internally by the checker. |
| addAliasedTypeAnnotation(EnumVal.class, StringVal.class, true); |
| |
| methods = new ValueMethodIdentifier(processingEnv); |
| |
| if (this.getClass() == ValueAnnotatedTypeFactory.class) { |
| this.postInit(); |
| } |
| } |
| |
| /** Gets a helper object that holds references to methods with special handling. */ |
| ValueMethodIdentifier getMethodIdentifier() { |
| return methods; |
| } |
| |
| @Override |
| public AnnotationMirror canonicalAnnotation(AnnotationMirror anno) { |
| if (AnnotationUtils.areSameByName(anno, MINLEN_NAME)) { |
| int from = getMinLenValue(anno); |
| return createArrayLenRangeAnnotation(from, Integer.MAX_VALUE); |
| } |
| |
| return super.canonicalAnnotation(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, |
| MatchesRegex.class, |
| DoubleVal.class, |
| BottomVal.class, |
| UnknownVal.class, |
| IntRangeFromPositive.class, |
| IntRangeFromNonNegative.class, |
| IntRangeFromGTENegativeOne.class, |
| PolyValue.class)); |
| } |
| |
| @Override |
| public CFTransfer createFlowTransferFunction( |
| CFAbstractAnalysis<CFValue, CFStore, CFTransfer> analysis) { |
| return new ValueTransfer(analysis); |
| } |
| |
| @Override |
| protected QualifierHierarchy createQualifierHierarchy() { |
| return new ValueQualifierHierarchy(this, this.getSupportedTypeQualifiers()); |
| } |
| |
| @Override |
| protected TypeHierarchy createTypeHierarchy() { |
| // This is a lot of code to replace annotations so that annotations that are equivalent |
| // qualifiers are the same annotation. |
| return new DefaultTypeHierarchy( |
| checker, |
| getQualifierHierarchy(), |
| checker.getBooleanOption("ignoreRawTypeArguments", true), |
| checker.hasOption("invariantArrays")) { |
| @Override |
| public StructuralEqualityComparer createEqualityComparer() { |
| return new StructuralEqualityComparer(areEqualVisitHistory) { |
| @Override |
| protected boolean arePrimeAnnosEqual( |
| AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { |
| type1.replaceAnnotation( |
| convertToUnknown( |
| convertSpecialIntRangeToStandardIntRange( |
| type1.getAnnotationInHierarchy(UNKNOWNVAL)))); |
| type2.replaceAnnotation( |
| convertToUnknown( |
| convertSpecialIntRangeToStandardIntRange( |
| type2.getAnnotationInHierarchy(UNKNOWNVAL)))); |
| |
| return super.arePrimeAnnosEqual(type1, type2); |
| } |
| }; |
| } |
| }; |
| } |
| |
| @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, minLenFieldInvariantFieldElement, String.class); |
| List<Integer> minlens = |
| AnnotationUtils.getElementValueArray( |
| fieldInvarAnno, minLenFieldInvariantMinLenElement, Integer.class); |
| List<AnnotationMirror> qualifiers = |
| CollectionsPlume.mapList( |
| (Integer minlen) -> createArrayLenRangeAnnotation(minlen, Integer.MAX_VALUE), minlens); |
| |
| 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 ParameterizedExecutableType methodFromUse( |
| ExpressionTree tree, ExecutableElement methodElt, AnnotatedTypeMirror receiverType) { |
| |
| ParameterizedExecutableType 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.executableType.getReturnType().replaceAnnotation(am); |
| } |
| return superPair; |
| } |
| |
| /** |
| * Finds the appropriate value for the {@code from} value of an annotated type mirror containing |
| * an {@code IntRange} annotation. |
| * |
| * @param atm an annotated type mirror that contains an {@code IntRange} annotation |
| * @return either the from value from the passed int range annotation, or the minimum value of the |
| * domain of the underlying type (i.e. Integer.MIN_VALUE if the underlying type is int) |
| */ |
| public long getFromValueFromIntRange(AnnotatedTypeMirror atm) { |
| TypeMirror type = atm.getUnderlyingType(); |
| long defaultValue = TypeKindUtils.minValue(toPrimitiveIntegralTypeKind(type)); |
| |
| AnnotationMirror intRangeAnno = atm.getAnnotation(IntRange.class); |
| return getIntRangeFromValue(intRangeAnno, defaultValue); |
| } |
| |
| /** |
| * Finds the appropriate value for the {@code to} value of an annotated type mirror containing an |
| * {@code IntRange} annotation. |
| * |
| * @param atm an annotated type mirror that contains an {@code IntRange} annotation |
| * @return either the to value from the passed int range annotation, or the maximum value of the |
| * domain of the underlying type (i.e. Integer.MAX_VALUE if the underlying type is int) |
| */ |
| public long getToValueFromIntRange(AnnotatedTypeMirror atm) { |
| TypeMirror type = atm.getUnderlyingType(); |
| long defaultValue = TypeKindUtils.maxValue(toPrimitiveIntegralTypeKind(type)); |
| |
| AnnotationMirror intRangeAnno = atm.getAnnotation(IntRange.class); |
| return getIntRangeToValue(intRangeAnno, defaultValue); |
| } |
| |
| /** |
| * Gets the from() element/field out of an IntRange annotation. The from() element/field must |
| * exist. Clients should call {@link #getFromValueFromIntRange} if it might not exist. |
| * |
| * @param intRangeAnno an IntRange annotation |
| * @return its from() element/field |
| */ |
| protected long getIntRangeFromValue(AnnotationMirror intRangeAnno) { |
| return AnnotationUtils.getElementValueLong(intRangeAnno, intRangeFromElement, Long.MIN_VALUE); |
| } |
| |
| /** |
| * Gets the from() element/field out of an IntRange annotation. The from() element/field must |
| * exist. Clients should call {@link #getFromValueFromIntRange} if it might not exist. |
| * |
| * @param intRangeAnno an IntRange annotation |
| * @param defaultValue the value to return if there is no from() element/field |
| * @return its from() element/field |
| */ |
| protected long getIntRangeFromValue(AnnotationMirror intRangeAnno, long defaultValue) { |
| return AnnotationUtils.getElementValueLong(intRangeAnno, intRangeFromElement, defaultValue); |
| } |
| |
| /** |
| * Gets the to() element/field out of an IntRange annotation. The to() element/field must exist. |
| * Clients should call {@link #getToValueFromIntRange} if it might not exist. |
| * |
| * @param intRangeAnno an IntRange annotation |
| * @param defaultValue the value to retur if there is no to() element/field |
| * @return its to() element/field |
| */ |
| protected long getIntRangeToValue(AnnotationMirror intRangeAnno, long defaultValue) { |
| return AnnotationUtils.getElementValueLong(intRangeAnno, intRangeToElement, defaultValue); |
| } |
| |
| /** |
| * Gets the to() element/field out of an IntRange annotation. The to() element/field must exist. |
| * Clients should call {@link #getToValueFromIntRange} if it might not exist. |
| * |
| * @param intRangeAnno an IntRange annotation |
| * @return its to() element/field |
| */ |
| protected long getIntRangeToValue(AnnotationMirror intRangeAnno) { |
| return AnnotationUtils.getElementValueLong(intRangeAnno, intRangeToElement, Long.MAX_VALUE); |
| } |
| |
| /** |
| * Gets the from() element/field out of an ArrayLenRange annotation. |
| * |
| * @param anno an ArrayLenRange annotation |
| * @return its from() element/field |
| */ |
| protected int getArrayLenRangeFromValue(AnnotationMirror anno) { |
| return AnnotationUtils.getElementValueInt(anno, arrayLenRangeFromElement, 0); |
| } |
| |
| /** |
| * Gets the to() element/field out of an ArrayLenRange annotation. |
| * |
| * @param anno an ArrayLenRange annotation |
| * @return its to() element/field |
| */ |
| protected int getArrayLenRangeToValue(AnnotationMirror anno) { |
| return AnnotationUtils.getElementValueInt(anno, arrayLenRangeToElement, Integer.MAX_VALUE); |
| } |
| |
| /** |
| * Gets the value() element/field out of a MinLen annotation. |
| * |
| * @param anno a MinLen annotation |
| * @return its value() element/field |
| */ |
| protected int getMinLenValueValue(AnnotationMirror anno) { |
| return AnnotationUtils.getElementValueInt(anno, minLenValueElement, 0); |
| } |
| |
| /** |
| * Determine the primitive integral TypeKind for the given integral type. |
| * |
| * @param type the type to convert, must be an integral type, boxed or primitive |
| * @return one of INT, SHORT, BYTE, CHAR, or LONG |
| */ |
| private static TypeKind toPrimitiveIntegralTypeKind(TypeMirror type) { |
| TypeKind typeKind = TypeKindUtils.primitiveOrBoxedToTypeKind(type); |
| if (typeKind != null && TypeKindUtils.isIntegral(typeKind)) { |
| return typeKind; |
| } |
| throw new BugInCF(type.toString() + " expected to be an integral type."); |
| } |
| |
| /** |
| * Gets the values stored in either an ArrayLen annotation (ints) or an IntVal/DoubleVal/etc. |
| * annotation (longs), and casts the result to a long. |
| * |
| * @param anno annotation mirror from which to get values |
| * @return the values in {@code anno} casted to longs |
| */ |
| /* package-private*/ List<Long> getArrayLenOrIntValue(AnnotationMirror anno) { |
| if (AnnotationUtils.areSameByName(anno, ARRAYLEN_NAME)) { |
| return CollectionsPlume.mapList(Integer::longValue, getArrayLength(anno)); |
| } else { |
| return getIntValues(anno); |
| } |
| } |
| |
| @Override |
| protected TreeAnnotator createTreeAnnotator() { |
| // Don't call super.createTreeAnnotator because it includes the PropagationTreeAnnotator. |
| // 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 LiteralTreeAnnotator(this).addStandardLiteralQualifiers(), |
| arrayCreation); |
| } |
| |
| /** |
| * Converts {@link IntRangeFromPositive}, {@link IntRangeFromNonNegative}, or {@link |
| * IntRangeFromGTENegativeOne} to {@link IntRange}. Any other annotation is just return. |
| * |
| * @param anm any annotation mirror |
| * @return the int range annotation is that equivalent to {@code anm}, or {@code anm} if one |
| * doesn't exist |
| */ |
| /* package-private */ AnnotationMirror convertSpecialIntRangeToStandardIntRange( |
| AnnotationMirror anm) { |
| if (AnnotationUtils.areSameByName(anm, INTRANGE_FROMPOS_NAME)) { |
| return createIntRangeAnnotation(1, Integer.MAX_VALUE); |
| } |
| |
| if (AnnotationUtils.areSameByName(anm, INTRANGE_FROMNONNEG_NAME)) { |
| return createIntRangeAnnotation(0, Integer.MAX_VALUE); |
| } |
| |
| if (AnnotationUtils.areSameByName(anm, INTRANGE_FROMGTENEGONE_NAME)) { |
| return createIntRangeAnnotation(-1, Integer.MAX_VALUE); |
| } |
| return anm; |
| } |
| |
| /** |
| * If {@code anno} is equivalent to UnknownVal, return UnknownVal; otherwise, return {@code anno}. |
| * |
| * @param anno any annotation mirror |
| * @return UnknownVal if {@code anno} is equivalent to it; otherwise, return {@code anno} |
| */ |
| /* package-private */ AnnotationMirror convertToUnknown(AnnotationMirror anno) { |
| if (AnnotationUtils.areSameByName(anno, ARRAYLENRANGE_NAME)) { |
| Range range = getRange(anno); |
| if (range.from == 0 && range.to >= Integer.MAX_VALUE) { |
| return UNKNOWNVAL; |
| } |
| } else if (AnnotationUtils.areSameByName(anno, INTRANGE_NAME)) { |
| Range range = getRange(anno); |
| if (range.isLongEverything()) { |
| return UNKNOWNVAL; |
| } |
| } |
| return anno; |
| } |
| |
| /** |
| * Returns the estimate for the length of a string or array with whose annotated type is {@code |
| * type}. |
| * |
| * @param type annotated typed |
| * @return the estimate for the length of a string or array with whose annotated type is {@code |
| * type}. |
| */ |
| /* package-private */ AnnotationMirror createArrayLengthResultAnnotation( |
| AnnotatedTypeMirror type) { |
| AnnotationMirror arrayAnno = type.getAnnotationInHierarchy(UNKNOWNVAL); |
| switch (AnnotationUtils.annotationName(arrayAnno)) { |
| case ARRAYLEN_NAME: |
| // array.length, where array : @ArrayLen(x) |
| List<Integer> lengths = getArrayLength(arrayAnno); |
| return createNumberAnnotationMirror(new ArrayList<>(lengths)); |
| case ARRAYLENRANGE_NAME: |
| // array.length, where array : @ArrayLenRange(x) |
| Range range = getRange(arrayAnno); |
| return createIntRangeAnnotation(range); |
| case STRINGVAL_NAME: |
| List<String> strings = getStringValues(arrayAnno); |
| List<Integer> lengthsS = ValueCheckerUtils.getLengthsForStringValues(strings); |
| return createNumberAnnotationMirror(new ArrayList<>(lengthsS)); |
| default: |
| return createIntRangeAnnotation(0, Integer.MAX_VALUE); |
| } |
| } |
| |
| /** |
| * Returns a constant value annotation with the {@code value}. The class of the annotation |
| * reflects the {@code resultType} given. |
| * |
| * @param resultType used to select which kind of value annotation is returned |
| * @param value value to use |
| * @return a constant value annotation with the {@code value} |
| */ |
| /* package-private */ 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 select 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} |
| */ |
| /* package-private */ 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.isEmpty()) { |
| return BOTTOMVAL; |
| } |
| |
| if (TypesUtils.isString(resultType)) { |
| List<String> stringVals = CollectionsPlume.mapList((Object o) -> (String) o, values); |
| return createStringAnnotation(stringVals); |
| } else if (TypesUtils.getClassFromType(resultType) == char[].class) { |
| List<String> stringVals = |
| CollectionsPlume.mapList( |
| (Object o) -> { |
| if (o instanceof char[]) { |
| return new String((char[]) o); |
| } else { |
| return o.toString(); |
| } |
| }, |
| values); |
| 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 = CollectionsPlume.mapList((Object o) -> (Boolean) o, values); |
| 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 = CollectionsPlume.withoutDuplicates(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}. |
| * |
| * @param intRangeAnno an {@code @IntRange} annotation |
| * @return an {@code @IntVal} annotation corresponding to the argument |
| */ |
| 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 = CollectionsPlume.withoutDuplicates(values); |
| if (values.size() > MAX_VALUES) { |
| return UNKNOWNVAL; |
| } else { |
| Collections.sort(values); |
| AnnotationBuilder builder = new AnnotationBuilder(processingEnv, DoubleVal.class); |
| builder.setValue("value", values); |
| return builder.build(); |
| } |
| } |
| |
| /** Convert an {@code @IntVal} annotation to a {@code @DoubleVal} annotation. */ |
| /* package-private */ AnnotationMirror convertIntValToDoubleVal(AnnotationMirror intValAnno) { |
| List<Long> intValues = getIntValues(intValAnno); |
| return createDoubleValAnnotation(convertLongListToDoubleList(intValues)); |
| } |
| |
| /** |
| * Convert a {@code List<Long>} to a {@code List<Double>}. |
| * |
| * @param intValues a list of long integers |
| * @return a list of double floating-point values |
| */ |
| /* package-private */ List<Double> convertLongListToDoubleList(List<Long> intValues) { |
| return CollectionsPlume.mapList(Long::doubleValue, intValues); |
| } |
| |
| /** |
| * 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. If values is larger than |
| * the max number of values allowed (10 by default), then an {@link ArrayLen} or an {@link |
| * ArrayLenRange} annotation is returned. |
| * |
| * @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 = CollectionsPlume.withoutDuplicates(values); |
| if (values.size() > MAX_VALUES) { |
| // Too many strings are replaced by their lengths |
| List<Integer> lengths = ValueCheckerUtils.getLengthsForStringValues(values); |
| return createArrayLenAnnotation(lengths); |
| } 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 = CollectionsPlume.withoutDuplicates(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 = CollectionsPlume.withoutDuplicates(values); |
| if (values.size() > MAX_VALUES) { |
| return UNKNOWNVAL; |
| } else { |
| // TODO: This seems wasteful. Why not create the 3 interesting AnnotationMirrors (with |
| // arguments {true}, {false}, and {true, false}, respectively) in advance and return one |
| // of them? (Maybe an advantage of this implementation is that it is identical to |
| // some other implementations and therefore might be less error-prone.) |
| 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 = CollectionsPlume.withoutDuplicates(values); |
| if (values.size() > MAX_VALUES) { |
| return UNKNOWNVAL; |
| } else { |
| List<Long> longValues = CollectionsPlume.mapList((Character value) -> (long) value, values); |
| return createIntValAnnotation(longValues); |
| } |
| } |
| |
| /** |
| * 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 doubleacters; duplicates are allowed and the values may be in any order |
| * @return a {@link IntVal} annotation using the values |
| */ |
| public AnnotationMirror createDoubleAnnotation(List<Double> values) { |
| if (values == null) { |
| return UNKNOWNVAL; |
| } |
| if (values.isEmpty()) { |
| return BOTTOMVAL; |
| } |
| values = CollectionsPlume.withoutDuplicates(values); |
| if (values.size() > MAX_VALUES) { |
| return UNKNOWNVAL; |
| } else { |
| return createDoubleValAnnotation(values); |
| } |
| } |
| |
| /** |
| * Returns an annotation that represents the given set of values. |
| * |
| * @param values a homogeneous list: every element of it has the same class |
| * @return an annotation that represents the given set of values |
| */ |
| 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 = CollectionsPlume.mapList(Number::longValue, values); |
| return createIntValAnnotation(intValues); |
| } else if (first instanceof Double || first instanceof Float) { |
| List<Double> intValues = CollectionsPlume.mapList(Number::doubleValue, values); |
| 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. |
| */ |
| /* package-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.isLongEverything()) { |
| 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); |
| } |
| } |
| |
| /** |
| * Creates the special {@link IntRangeFromPositive} annotation, which is only used as an alias for |
| * the Index Checker's {@link org.checkerframework.checker.index.qual.Positive} annotation. It is |
| * treated everywhere as an IntRange annotation, but is not checked when it appears as the left |
| * hand side of an assignment (because the Lower Bound Checker will check it). |
| */ |
| private AnnotationMirror createIntRangeFromPositive() { |
| AnnotationBuilder builder = new AnnotationBuilder(processingEnv, IntRangeFromPositive.class); |
| return builder.build(); |
| } |
| |
| /** |
| * Creates the special {@link IntRangeFromNonNegative} annotation, which is only used as an alias |
| * for the Index Checker's {@link org.checkerframework.checker.index.qual.NonNegative} annotation. |
| * It is treated everywhere as an IntRange annotation, but is not checked when it appears as the |
| * left hand side of an assignment (because the Lower Bound Checker will check it). |
| */ |
| private AnnotationMirror createIntRangeFromNonNegative() { |
| AnnotationBuilder builder = new AnnotationBuilder(processingEnv, IntRangeFromNonNegative.class); |
| return builder.build(); |
| } |
| |
| /** |
| * Creates the special {@link IntRangeFromGTENegativeOne} annotation, which is only used as an |
| * alias for the Index Checker's {@link org.checkerframework.checker.index.qual.GTENegativeOne} |
| * annotation. It is treated everywhere as an IntRange annotation, but is not checked when it |
| * appears as the left hand side of an assignment (because the Lower Bound Checker will check it). |
| */ |
| private AnnotationMirror createIntRangeFromGTENegativeOne() { |
| AnnotationBuilder builder = |
| new AnnotationBuilder(processingEnv, IntRangeFromGTENegativeOne.class); |
| return builder.build(); |
| } |
| |
| /** |
| * 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.isLongEverything() || !range.isWithinInteger()) { |
| return UNKNOWNVAL; |
| } else { |
| return createArrayLenRangeAnnotation( |
| Long.valueOf(range.from).intValue(), Long.valueOf(range.to).intValue()); |
| } |
| } |
| |
| /** |
| * Creates an {@code MatchesRegex} annotation for the given regular expressions. |
| * |
| * @param regexes a list of Java regular expressions |
| * @return a MatchesRegex annotation with those values |
| */ |
| public AnnotationMirror createMatchesRegexAnnotation(@Nullable List<@Regex String> regexes) { |
| if (regexes == null) { |
| return UNKNOWNVAL; |
| } |
| if (regexes.isEmpty()) { |
| return BOTTOMVAL; |
| } |
| AnnotationBuilder builder = new AnnotationBuilder(processingEnv, MatchesRegex.class); |
| builder.setValue("value", regexes.toArray(new String[0])); |
| return builder.build(); |
| } |
| |
| /** |
| * Converts an {@code @StringVal} annotation to an {@code @ArrayLenRange} annotation. |
| * |
| * @param stringValAnno a StringVal annotation |
| * @return an ArrayLenRange annotation representing the possible lengths of the values of the |
| * given StringVal annotation |
| */ |
| /* package-private */ AnnotationMirror convertStringValToArrayLenRange( |
| AnnotationMirror stringValAnno) { |
| List<String> values = getStringValues(stringValAnno); |
| List<Integer> lengths = ValueCheckerUtils.getLengthsForStringValues(values); |
| return createArrayLenRangeAnnotation(Collections.min(lengths), Collections.max(lengths)); |
| } |
| |
| /** |
| * Converts an {@code @StringVal} annotation to an {@code @ArrayLen} annotation. If the |
| * {@code @StringVal} annotation contains string values of more than MAX_VALUES distinct lengths, |
| * {@code @ArrayLenRange} annotation is returned instead. |
| */ |
| /* package-private */ AnnotationMirror convertStringValToArrayLen( |
| AnnotationMirror stringValAnno) { |
| List<String> values = getStringValues(stringValAnno); |
| return createArrayLenAnnotation(ValueCheckerUtils.getLengthsForStringValues(values)); |
| } |
| |
| /** |
| * Converts an {@code StringVal} annotation to an {@code MatchesRegex} annotation that matches |
| * exactly the string values listed in the {@code StringVal}. |
| * |
| * @param stringValAnno a StringVal annotation |
| * @return an equivalent MatchesReges annotation |
| */ |
| /* package-private */ AnnotationMirror convertStringValToMatchesRegex( |
| AnnotationMirror stringValAnno) { |
| List<String> values = getStringValues(stringValAnno); |
| List<@Regex String> valuesAsRegexes = CollectionsPlume.mapList(Pattern::quote, values); |
| return createMatchesRegexAnnotation(valuesAsRegexes); |
| } |
| |
| /** |
| * Converts an {@code @ArrayLen} annotation to an {@code @ArrayLenRange} annotation. |
| * |
| * @param arrayLenAnno an ArrayLen annotation |
| * @return an ArrayLenRange annotation representing the bounds of the given ArrayLen 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 {@link Range} bounded by the values specified in the given {@code @Range} annotation. |
| * Also returns an appropriate range if an {@code @IntVal} annotation is passed. Returns {@code |
| * null} if the annotation is null or if the annotation is not an {@code IntRange}, {@code |
| * IntRangeFromPositive}, {@code IntVal}, or {@code ArrayLenRange}. |
| * |
| * @param rangeAnno a {@code @Range} annotation |
| * @return the {@link Range} that the annotation represents |
| */ |
| public Range getRange(AnnotationMirror rangeAnno) { |
| if (rangeAnno == null) { |
| return null; |
| } |
| switch (AnnotationUtils.annotationName(rangeAnno)) { |
| case INTRANGE_FROMPOS_NAME: |
| return Range.create(1, Integer.MAX_VALUE); |
| case INTRANGE_FROMNONNEG_NAME: |
| return Range.create(0, Integer.MAX_VALUE); |
| case INTRANGE_FROMGTENEGONE_NAME: |
| return Range.create(-1, Integer.MAX_VALUE); |
| case INTVAL_NAME: |
| return ValueCheckerUtils.getRangeFromValues(getIntValues(rangeAnno)); |
| case INTRANGE_NAME: |
| // Assume rangeAnno is well-formed, i.e., 'from' is less than or equal to 'to'. |
| return Range.create(getIntRangeFromValue(rangeAnno), getIntRangeToValue(rangeAnno)); |
| case ARRAYLENRANGE_NAME: |
| return Range.create( |
| getArrayLenRangeFromValue(rangeAnno), getArrayLenRangeToValue(rangeAnno)); |
| default: |
| return null; |
| } |
| } |
| |
| /** |
| * Returns the set of possible values as a sorted list 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 |
| * @return the possible values, deduplicated and sorted |
| */ |
| public List<Long> getIntValues(AnnotationMirror intAnno) { |
| if (intAnno == null) { |
| return null; |
| } |
| List<Long> list = AnnotationUtils.getElementValueArray(intAnno, intValValueElement, Long.class); |
| list = CollectionsPlume.withoutDuplicates(list); |
| return list; |
| } |
| |
| /** |
| * Returns the set of possible values as a sorted list 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 |
| * @return the possible values, deduplicated and sorted |
| */ |
| public List<Double> getDoubleValues(AnnotationMirror doubleAnno) { |
| if (doubleAnno == null) { |
| return null; |
| } |
| List<Double> list = |
| AnnotationUtils.getElementValueArray(doubleAnno, doubleValValueElement, Double.class); |
| list = CollectionsPlume.withoutDuplicates(list); |
| return list; |
| } |
| |
| /** |
| * Returns the set of possible array lengths as a sorted list 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 |
| * @return the possible array lengths, deduplicated and sorted |
| */ |
| public List<Integer> getArrayLength(AnnotationMirror arrayAnno) { |
| if (arrayAnno == null) { |
| return null; |
| } |
| List<Integer> list = |
| AnnotationUtils.getElementValueArray(arrayAnno, arrayLenValueElement, Integer.class); |
| list = CollectionsPlume.withoutDuplicates(list); |
| return list; |
| } |
| |
| /** |
| * Returns the set of possible values as a sorted list 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 |
| * @return the values represented by the given {@code @IntVal} annotation |
| */ |
| public List<Character> getCharValues(AnnotationMirror intAnno) { |
| if (intAnno == null) { |
| return Collections.emptyList(); |
| } |
| List<Long> intValues = |
| AnnotationUtils.getElementValueArray(intAnno, intValValueElement, Long.class); |
| List<Character> charValues = |
| CollectionsPlume.mapList((Long i) -> (char) i.intValue(), intValues); |
| Collections.sort(charValues); |
| // TODO: Should this be an unmodifiable list? |
| return new ArrayList<>(charValues); |
| } |
| |
| /** |
| * Returns the single possible boolean value, or null if there is not exactly one possible value. |
| * |
| * @see #getBooleanValues |
| * @param boolAnno a {@code @BoolVal} annotation, or null |
| * @return the single possible boolean value, on null if that is not the case |
| */ |
| public Boolean getBooleanValue(AnnotationMirror boolAnno) { |
| if (boolAnno == null) { |
| return null; |
| } |
| List<Boolean> boolValues = |
| AnnotationUtils.getElementValueArray(boolAnno, boolValValueElement, Boolean.class); |
| Set<Boolean> boolSet = new TreeSet<>(boolValues); |
| if (boolSet.size() == 1) { |
| return boolSet.iterator().next(); |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the set of possible boolean values as a sorted list 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 |
| * @return a singleton or empty list of possible boolean values, or null |
| */ |
| public @Nullable List<Boolean> getBooleanValues(AnnotationMirror boolAnno) { |
| if (boolAnno == null) { |
| return Collections.emptyList(); |
| } |
| List<Boolean> boolValues = |
| AnnotationUtils.getElementValueArray(boolAnno, boolValValueElement, Boolean.class); |
| if (boolValues.size() < 2) { |
| return boolValues; |
| } |
| // Remove duplicates. |
| Set<Boolean> boolSet = new TreeSet<>(boolValues); |
| if (boolSet.size() > 1) { |
| // boolSet={true,false}; |
| return null; |
| } |
| return new ArrayList<>(boolSet); |
| } |
| |
| /** |
| * Returns the set of possible values as a sorted list 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 stringAnno a {@code @StringVal} annotation, or null |
| * @return the possible values, deduplicated and sorted |
| */ |
| public List<String> getStringValues(AnnotationMirror stringAnno) { |
| if (stringAnno == null) { |
| return null; |
| } |
| List<String> list = |
| AnnotationUtils.getElementValueArray(stringAnno, stringValValueElement, String.class); |
| list = CollectionsPlume.withoutDuplicates(list); |
| return list; |
| } |
| |
| /** |
| * Returns the set of possible values as a sorted list 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 matchesRegexAnno a {@code @MatchesRegex} annotation, or null |
| * @return the possible values, deduplicated and sorted |
| */ |
| public List<String> getMatchesRegexValues(AnnotationMirror matchesRegexAnno) { |
| if (matchesRegexAnno == null) { |
| return null; |
| } |
| List<String> list = |
| AnnotationUtils.getElementValueArray( |
| matchesRegexAnno, matchesRegexValueElement, String.class); |
| list = CollectionsPlume.withoutDuplicates(list); |
| return list; |
| } |
| |
| public boolean isIntRange(Set<AnnotationMirror> anmSet) { |
| for (AnnotationMirror anm : anmSet) { |
| if (isIntRange(anm)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Returns true if {@code anno} is an {@link IntRange}, {@link IntRangeFromPositive}, {@link |
| * IntRangeFromNonNegative}, or {@link IntRangeFromGTENegativeOne}. |
| * |
| * @param anno annotation mirror |
| * @return true if {@code anno} is an {@link IntRange}, {@link IntRangeFromPositive}, {@link |
| * IntRangeFromNonNegative}, or {@link IntRangeFromGTENegativeOne} |
| */ |
| public boolean isIntRange(AnnotationMirror anno) { |
| String name = AnnotationUtils.annotationName(anno); |
| return name.equals(INTRANGE_NAME) |
| || name.equals(INTRANGE_FROMPOS_NAME) |
| || name.equals(INTRANGE_FROMNONNEG_NAME) |
| || name.equals(INTRANGE_FROMGTENEGONE_NAME); |
| } |
| |
| public int getMinLenValue(AnnotatedTypeMirror atm) { |
| return getMinLenValue(atm.getAnnotationInHierarchy(UNKNOWNVAL)); |
| } |
| |
| /** |
| * Used to find the maximum length of an array. Returns null if there is no minimum length known, |
| * or if the passed annotation is null. |
| */ |
| public Integer getMaxLenValue(AnnotationMirror annotation) { |
| if (annotation == null) { |
| return null; |
| } |
| switch (AnnotationUtils.annotationName(annotation)) { |
| case ARRAYLENRANGE_NAME: |
| return Long.valueOf(getRange(annotation).to).intValue(); |
| case ARRAYLEN_NAME: |
| return Collections.max(getArrayLength(annotation)); |
| case STRINGVAL_NAME: |
| return Collections.max( |
| ValueCheckerUtils.getLengthsForStringValues(getStringValues(annotation))); |
| default: |
| return null; |
| } |
| } |
| |
| /** |
| * Finds a minimum length of an array specified by the provided annotation. 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#canonicalAnnotation(AnnotationMirror)}, which transforms |
| * {@link MinLen} annotations into {@link ArrayLenRange} annotations. |
| */ |
| private Integer getSpecifiedMinLenValue(AnnotationMirror annotation) { |
| if (annotation == null) { |
| return null; |
| } |
| switch (AnnotationUtils.annotationName(annotation)) { |
| case MINLEN_NAME: |
| return getMinLenValueValue(annotation); |
| case ARRAYLENRANGE_NAME: |
| return Long.valueOf(getRange(annotation).from).intValue(); |
| case ARRAYLEN_NAME: |
| return Collections.min(getArrayLength(annotation)); |
| case STRINGVAL_NAME: |
| return Collections.min( |
| ValueCheckerUtils.getLengthsForStringValues(getStringValues(annotation))); |
| default: |
| return null; |
| } |
| } |
| |
| /** |
| * Used to find the minimum length of an array, which is useful for array bounds checking. Returns |
| * 0 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#canonicalAnnotation(AnnotationMirror)}, which transforms |
| * {@link MinLen} annotations into {@link ArrayLenRange} annotations. |
| */ |
| public int getMinLenValue(AnnotationMirror annotation) { |
| Integer minLen = getSpecifiedMinLenValue(annotation); |
| if (minLen == null || minLen < 0) { |
| return 0; |
| } else { |
| return minLen; |
| } |
| } |
| |
| /** |
| * Returns the minimum length of an array. |
| * |
| * @param annotations the annotations on the array expression |
| * @return the minimum length of an array |
| */ |
| public int getMinLenValue(Set<AnnotationMirror> annotations) { |
| int result = 0; |
| for (AnnotationMirror annotation : annotations) { |
| Integer minLen = getSpecifiedMinLenValue(annotation); |
| if (minLen != null) { |
| result = Integer.min(result, minLen); |
| } |
| } |
| if (result < 0) { |
| return 0; |
| } else { |
| return result; |
| } |
| } |
| |
| /** |
| * 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. |
| * |
| * @param atm annotated type |
| * @return the smallest possible integral for which the {@code atm} could be the type |
| */ |
| public Long getMinimumIntegralValue(AnnotatedTypeMirror atm) { |
| AnnotationMirror anm = atm.getAnnotationInHierarchy(UNKNOWNVAL); |
| if (AnnotationUtils.areSameByName(anm, INTVAL_NAME)) { |
| List<Long> possibleValues = getIntValues(anm); |
| return Collections.min(possibleValues); |
| } else if (isIntRange(anm)) { |
| 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 sequenceExpression Java expression |
| * @param tree expression tree or variable declaration |
| * @param currentPath path to local scope |
| * @return min length of sequenceExpression or 0 |
| */ |
| public int getMinLenFromString(String sequenceExpression, Tree tree, TreePath currentPath) { |
| AnnotationMirror lengthAnno; |
| JavaExpression expressionObj; |
| try { |
| expressionObj = parseJavaExpressionString(sequenceExpression, currentPath); |
| } catch (JavaExpressionParseException e) { |
| // ignore parse errors and return 0. |
| return 0; |
| } |
| |
| if (expressionObj instanceof ValueLiteral) { |
| ValueLiteral sequenceLiteral = (ValueLiteral) expressionObj; |
| Object sequenceLiteralValue = sequenceLiteral.getValue(); |
| if (sequenceLiteralValue instanceof String) { |
| return ((String) sequenceLiteralValue).length(); |
| } |
| } else if (expressionObj instanceof ArrayCreation) { |
| ArrayCreation arrayCreation = (ArrayCreation) expressionObj; |
| // This is only expected to support array creations in varargs methods |
| return arrayCreation.getInitializers().size(); |
| } else if (expressionObj instanceof ArrayAccess) { |
| List<? extends AnnotationMirror> annoList = expressionObj.getType().getAnnotationMirrors(); |
| for (AnnotationMirror anno : annoList) { |
| String ANNO_NAME = AnnotationUtils.annotationName(anno); |
| if (ANNO_NAME.equals(MINLEN_NAME)) { |
| return getMinLenValue(canonicalAnnotation(anno)); |
| } else if (ANNO_NAME.equals(ARRAYLEN_NAME) || ANNO_NAME.equals(ARRAYLENRANGE_NAME)) { |
| return getMinLenValue(anno); |
| } |
| } |
| } |
| |
| lengthAnno = getAnnotationFromJavaExpression(expressionObj, tree, ArrayLenRange.class); |
| if (lengthAnno == null) { |
| lengthAnno = getAnnotationFromJavaExpression(expressionObj, tree, ArrayLen.class); |
| } |
| if (lengthAnno == null) { |
| lengthAnno = getAnnotationFromJavaExpression(expressionObj, tree, StringVal.class); |
| } |
| |
| if (lengthAnno == null) { |
| // Could not find a more precise type, so return 0; |
| return 0; |
| } |
| |
| return getMinLenValue(lengthAnno); |
| } |
| |
| /** |
| * Returns the annotation type mirror for the type of {@code expressionTree} with default |
| * annotations applied. |
| */ |
| @Override |
| public AnnotatedTypeMirror getDummyAssignedTo(ExpressionTree expressionTree) { |
| TypeMirror type = TreeUtils.typeOf(expressionTree); |
| if (type.getKind() != TypeKind.VOID) { |
| AnnotatedTypeMirror atm = type(expressionTree); |
| addDefaultAnnotations(atm); |
| return atm; |
| } |
| return null; |
| } |
| } |