All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.checkerframework.common.value.ValueTypeAnnotator Maven / Gradle / Ivy

Go to download

The Checker Framework enhances Java's type system to make it more powerful and useful. This lets software developers detect and prevent errors in their Java programs. The Checker Framework includes compiler plug-ins ("checkers") that find bugs or verify their absence. It also permits you to write your own compiler plug-ins.

There is a newer version: 3.44.0
Show newest version
package org.checkerframework.common.value;

import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.common.value.util.Range;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.typeannotator.TypeAnnotator;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.TypeKindUtils;
import org.checkerframework.javacutil.TypesUtils;

/**
 * Performs pre-processing on annotations written by users, replacing illegal annotations by legal
 * ones.
 */
class ValueTypeAnnotator extends TypeAnnotator {

  /** The type factory to use. Shadows the field from the superclass with a more specific type. */
  @SuppressWarnings("HidingField")
  protected final ValueAnnotatedTypeFactory typeFactory;

  /**
   * Construct a new ValueTypeAnnotator.
   *
   * @param typeFactory the type factory to use
   */
  protected ValueTypeAnnotator(ValueAnnotatedTypeFactory typeFactory) {
    super(typeFactory);
    this.typeFactory = typeFactory;
  }

  @Override
  protected Void scan(AnnotatedTypeMirror type, Void aVoid) {
    replaceWithNewAnnoInSpecialCases(type);
    return super.scan(type, aVoid);
  }

  /**
   * This method performs pre-processing on annotations written by users.
   *
   * 

If any *Val annotation has > MAX_VALUES number of values provided, replaces the * annotation by @IntRange for integral types, @ArrayLenRange for arrays, @ArrayLen * or @ArrayLenRange for strings, and @UnknownVal for all other types. Works together with {@link * ValueVisitor#visitAnnotation(com.sun.source.tree.AnnotationTree, Void)} which issues warnings * to users in these cases. * *

If any @IntRange or @ArrayLenRange annotation has incorrect parameters, e.g. the value * "from" is greater than the value "to", replaces the annotation by {@code @BottomVal}. The * {@link ValueVisitor#visitAnnotation(com.sun.source.tree.AnnotationTree, Void)} raises an error * to users if the annotation was user-written. * *

If any @ArrayLen annotation has a negative number, replaces the annotation by {@code * BottomVal}. The {@link ValueVisitor#visitAnnotation(com.sun.source.tree.AnnotationTree, Void)} * raises an error to users if the annotation was user-written. * *

If a user only writes one side of an {@code IntRange} annotation, this method also computes * an appropriate default based on the underlying type for the other side of the range. For * instance, if the user writes {@code @IntRange(from = 1) short x;} then this method will * translate the annotation to {@code @IntRange(from = 1, to = Short.MAX_VALUE}. */ private void replaceWithNewAnnoInSpecialCases(AnnotatedTypeMirror atm) { AnnotationMirror anno = atm.getAnnotationInHierarchy(typeFactory.UNKNOWNVAL); if (anno == null || anno.getElementValues().isEmpty()) { return; } if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.INTVAL_NAME)) { List values = typeFactory.getIntValues(anno); if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) { atm.replaceAnnotation(typeFactory.createIntRangeAnnotation(Range.create(values))); } } else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.ARRAYLEN_NAME)) { List values = typeFactory.getArrayLength(anno); if (values.isEmpty()) { atm.replaceAnnotation(typeFactory.BOTTOMVAL); } else if (Collections.min(values) < 0) { atm.replaceAnnotation(typeFactory.BOTTOMVAL); } else if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) { atm.replaceAnnotation(typeFactory.createArrayLenRangeAnnotation(Range.create(values))); } } else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.INTRANGE_NAME)) { TypeMirror underlyingType = atm.getUnderlyingType(); // If the underlying type is neither a primitive integral type nor boxed integral type, // return without making changes. TypesUtils.isIntegralPrimitiveOrBoxed fails if passed // a non-primitive type that is not a declared type, so it cannot be called directly. if (!TypeKindUtils.isIntegral(underlyingType.getKind()) && (underlyingType.getKind() != TypeKind.DECLARED || !TypesUtils.isIntegralPrimitiveOrBoxed(underlyingType))) { return; } // Compute appropriate defaults for integral ranges. long from = typeFactory.getFromValueFromIntRange(atm); long to = typeFactory.getToValueFromIntRange(atm); if (from > to) { // `from > to` either indicates a user error when writing an annotation or an error in the // checker's implementation. `-from` should always be <= to. ValueVisitor#validateType will // issue an error. atm.replaceAnnotation(typeFactory.BOTTOMVAL); } else { // Always do a replacement of the annotation here so that the defaults calculated above are // correctly added to the annotation (assuming the annotation is well-formed). atm.replaceAnnotation(typeFactory.createIntRangeAnnotation(from, to)); } } else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME)) { int from = typeFactory.getArrayLenRangeFromValue(anno); int to = typeFactory.getArrayLenRangeToValue(anno); if (from > to) { // `from > to` either indicates a user error when writing an annotation or an error in the // checker's implementation `-from` should always be <= to. ValueVisitor#validateType will // issue an error. atm.replaceAnnotation(typeFactory.BOTTOMVAL); } else if (from < 0) { // No array can have a length less than 0. Any time the type includes a from // less than zero, it must indicate imprecision in the checker. atm.replaceAnnotation(typeFactory.createArrayLenRangeAnnotation(0, to)); } } else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.STRINGVAL_NAME)) { // The annotation is StringVal. If there are too many elements, // ArrayLen or ArrayLenRange is used. List values = typeFactory.getStringValues(anno); if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) { List lengths = ValueCheckerUtils.getLengthsForStringValues(values); atm.replaceAnnotation(typeFactory.createArrayLenAnnotation(lengths)); } } else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME)) { // If the annotation contains an invalid regex, replace it with bottom. ValueVisitor // will issue a warning where the annotation was written. List regexes = AnnotationUtils.getElementValueArray( anno, typeFactory.matchesRegexValueElement, String.class); for (String regex : regexes) { try { Pattern.compile(regex); } catch (PatternSyntaxException pse) { atm.replaceAnnotation(typeFactory.BOTTOMVAL); break; } } } else { // In here the annotation is @*Val where (*) is not Int, String but other types // (Bool, Double, or Enum). // Therefore we extract its values in a generic way to check its size. @SuppressWarnings("deprecation") // concrete annotation class is not known List values = AnnotationUtils.getElementValueArray(anno, "value", Object.class, false); if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) { atm.replaceAnnotation(typeFactory.UNKNOWNVAL); } } } }